Merge "Update some descriptions and refine code"
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index ac777d7..bd8fe7c 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -90,6 +90,8 @@
compressible: true,
androidManifest: "AndroidManifest.xml",
+
+ compat_configs: ["connectivity-platform-compat-config"],
}
apex_key {
diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
index 22d2c5d..b865a8e 100644
--- a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
@@ -30,9 +30,9 @@
import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
-import com.android.networkstack.tethering.TetherStatsValue;
/**
* Bpf coordinator class for API shims.
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index 5afb862..0683e5e 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -33,6 +33,8 @@
import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
import com.android.networkstack.tethering.BpfUtils;
@@ -42,8 +44,6 @@
import com.android.networkstack.tethering.TetherDownstream6Key;
import com.android.networkstack.tethering.TetherLimitKey;
import com.android.networkstack.tethering.TetherLimitValue;
-import com.android.networkstack.tethering.TetherStatsKey;
-import com.android.networkstack.tethering.TetherStatsValue;
import com.android.networkstack.tethering.TetherUpstream6Key;
import java.io.FileDescriptor;
diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
index 915e210..69cbab5 100644
--- a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
+++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
@@ -25,9 +25,9 @@
import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
-import com.android.networkstack.tethering.TetherStatsValue;
/**
* Bpf coordinator class for API shims.
diff --git a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
index b4e3ba4..836761f 100644
--- a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
@@ -36,4 +36,5 @@
void onTetherStatesChanged(in TetherStatesParcel states);
void onTetherClientsChanged(in List<TetheredClient> clients);
void onOffloadStatusChanged(int status);
+ void onSupportedTetheringTypes(long supportedBitmap);
}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
index 253eacb..f33f846 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
@@ -26,7 +26,7 @@
* @hide
*/
parcelable TetheringCallbackStartedParcel {
- boolean tetheringSupported;
+ long supportedTypes;
Network upstreamNetwork;
TetheringConfigurationParcel config;
TetherStatesParcel states;
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 6f9b33e..b3f0cf2 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -183,6 +183,12 @@
*/
public static final int TETHERING_WIGIG = 6;
+ /**
+ * The int value of last tethering type.
+ * @hide
+ */
+ public static final int MAX_TETHERING_TYPE = TETHERING_WIGIG;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
@@ -520,6 +526,9 @@
}
@Override
+ public void onSupportedTetheringTypes(long supportedBitmap) { }
+
+ @Override
public void onUpstreamChanged(Network network) { }
@Override
@@ -1033,15 +1042,29 @@
/**
* Called when tethering supported status changed.
*
+ * <p>This callback will be called immediately after the callback is
+ * registered, and never be called if there is changes afterward.
+ *
+ * <p>Tethering may be disabled via system properties, device configuration, or device
+ * policy restrictions.
+ *
+ * @param supported whether any tethering type is supported.
+ */
+ default void onTetheringSupported(boolean supported) {}
+
+ /**
+ * Called when tethering supported status changed.
+ *
* <p>This will be called immediately after the callback is registered, and may be called
* multiple times later upon changes.
*
* <p>Tethering may be disabled via system properties, device configuration, or device
* policy restrictions.
*
- * @param supported The new supported status
+ * @param supportedTypes a set of @TetheringType which is supported.
+ * @hide
*/
- default void onTetheringSupported(boolean supported) {}
+ default void onSupportedTetheringTypes(@NonNull Set<Integer> supportedTypes) {}
/**
* Called when tethering upstream changed.
@@ -1339,7 +1362,8 @@
@Override
public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
executor.execute(() -> {
- callback.onTetheringSupported(parcel.tetheringSupported);
+ callback.onSupportedTetheringTypes(unpackBits(parcel.supportedTypes));
+ callback.onTetheringSupported(parcel.supportedTypes != 0);
callback.onUpstreamChanged(parcel.upstreamNetwork);
sendErrorCallbacks(parcel.states);
sendRegexpsChanged(parcel.config);
@@ -1358,6 +1382,13 @@
});
}
+ @Override
+ public void onSupportedTetheringTypes(long supportedBitmap) {
+ executor.execute(() -> {
+ callback.onSupportedTetheringTypes(unpackBits(supportedBitmap));
+ });
+ }
+
private void sendRegexpsChanged(TetheringConfigurationParcel parcel) {
callback.onTetherableInterfaceRegexpsChanged(new TetheringInterfaceRegexps(
parcel.tetherableBluetoothRegexs,
@@ -1396,6 +1427,23 @@
}
/**
+ * Unpack bitmap to a set of bit position intergers.
+ * @hide
+ */
+ public static ArraySet<Integer> unpackBits(long val) {
+ final ArraySet<Integer> result = new ArraySet<>(Long.bitCount(val));
+ int bitPos = 0;
+ while (val != 0) {
+ if ((val & 1) == 1) result.add(bitPos);
+
+ val = val >>> 1;
+ bitPos++;
+ }
+
+ return result;
+ }
+
+ /**
* Remove tethering event callback previously registered with
* {@link #registerTetheringEventCallback}.
*
diff --git a/Tethering/proguard.flags b/Tethering/proguard.flags
index 7b5ae0d..2905e28 100644
--- a/Tethering/proguard.flags
+++ b/Tethering/proguard.flags
@@ -12,6 +12,11 @@
native <methods>;
}
+# Ensure runtime-visible field annotations are kept when using R8 full mode.
+-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
+-keep interface com.android.networkstack.tethering.util.Struct$Field {
+ *;
+}
-keepclassmembers public class * extends com.android.networkstack.tethering.util.Struct {
*;
}
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index f8a1094..ecb6478 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -65,9 +65,12 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.U32;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.net.module.util.netlink.ConntrackMessage;
import com.android.net.module.util.netlink.NetlinkConstants;
import com.android.net.module.util.netlink.NetlinkSocket;
@@ -118,6 +121,8 @@
private static final String TETHER_LIMIT_MAP_PATH = makeMapPath("limit");
private static final String TETHER_ERROR_MAP_PATH = makeMapPath("error");
private static final String TETHER_DEV_MAP_PATH = makeMapPath("dev");
+ private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
+ private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
// Using "," as a separator is safe because base64 characters are [0-9a-zA-Z/=+].
private static final String DUMP_BASE64_DELIMITER = ",";
@@ -1072,7 +1077,8 @@
}
}
- private String ipv4RuleToBase64String(Tether4Key key, Tether4Value value) {
+ private <K extends Struct, V extends Struct> String bpfMapEntryToBase64String(
+ final K key, final V value) {
final byte[] keyBytes = key.writeToBytes();
final String keyBase64Str = Base64.encodeToString(keyBytes, Base64.DEFAULT)
.replace("\n", "");
@@ -1083,28 +1089,45 @@
return keyBase64Str + DUMP_BASE64_DELIMITER + valueBase64Str;
}
- private void dumpRawIpv4ForwardingRuleMap(
- BpfMap<Tether4Key, Tether4Value> map, IndentingPrintWriter pw) throws ErrnoException {
+ private <K extends Struct, V extends Struct> void dumpRawMap(BpfMap<K, V> map,
+ IndentingPrintWriter pw) throws ErrnoException {
if (map == null) {
- pw.println("No IPv4 support");
+ pw.println("No BPF support");
return;
}
if (map.isEmpty()) {
- pw.println("No rules");
+ pw.println("No entries");
return;
}
- map.forEach((k, v) -> pw.println(ipv4RuleToBase64String(k, v)));
+ map.forEach((k, v) -> pw.println(bpfMapEntryToBase64String(k, v)));
}
/**
* Dump raw BPF map in base64 encoded strings. For test only.
+ * Only allow to dump one map path once.
+ * Format:
+ * $ dumpsys tethering bpfRawMap --<map name>
*/
- public void dumpRawMap(@NonNull IndentingPrintWriter pw) {
- try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
- // TODO: dump downstream map.
- dumpRawIpv4ForwardingRuleMap(upstreamMap, pw);
- } catch (ErrnoException e) {
- pw.println("Error dumping IPv4 map: " + e);
+ public void dumpRawMap(@NonNull IndentingPrintWriter pw, @Nullable String[] args) {
+ // TODO: consider checking the arg order that <map name> is after "bpfRawMap". Probably
+ // it is okay for now because this is used by test only and test is supposed to use
+ // expected argument order.
+ // TODO: dump downstream4 map.
+ if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_STATS)) {
+ try (BpfMap<TetherStatsKey, TetherStatsValue> statsMap = mDeps.getBpfStatsMap()) {
+ dumpRawMap(statsMap, pw);
+ } catch (ErrnoException e) {
+ pw.println("Error dumping stats map: " + e);
+ }
+ return;
+ }
+ if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_UPSTREAM4)) {
+ try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
+ dumpRawMap(upstreamMap, pw);
+ } catch (ErrnoException e) {
+ pw.println("Error dumping IPv4 map: " + e);
+ }
+ return;
}
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherStatsKey.java b/Tethering/src/com/android/networkstack/tethering/TetherStatsKey.java
deleted file mode 100644
index 5442480..0000000
--- a/Tethering/src/com/android/networkstack/tethering/TetherStatsKey.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.networkstack.tethering;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-/** The key of BpfMap which is used for tethering stats. */
-public class TetherStatsKey extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long ifindex; // upstream interface index
-
- public TetherStatsKey(final long ifindex) {
- this.ifindex = ifindex;
- }
-
- // TODO: remove equals, hashCode and toString once aosp/1536721 is merged.
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
-
- if (!(obj instanceof TetherStatsKey)) return false;
-
- final TetherStatsKey that = (TetherStatsKey) obj;
-
- return ifindex == that.ifindex;
- }
-
- @Override
- public int hashCode() {
- return Long.hashCode(ifindex);
- }
-
- @Override
- public String toString() {
- return String.format("ifindex: %d", ifindex);
- }
-}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherStatsValue.java b/Tethering/src/com/android/networkstack/tethering/TetherStatsValue.java
deleted file mode 100644
index 844d2e8..0000000
--- a/Tethering/src/com/android/networkstack/tethering/TetherStatsValue.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.networkstack.tethering;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-/** The key of BpfMap which is used for tethering stats. */
-public class TetherStatsValue extends Struct {
- // Use the signed long variable to store the uint64 stats from stats BPF map.
- // U63 is enough for each data element even at 5Gbps for ~468 years.
- // 2^63 / (5 * 1000 * 1000 * 1000) * 8 / 86400 / 365 = 468.
- @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 rxErrors;
- @Field(order = 3, type = Type.U63)
- public final long txPackets;
- @Field(order = 4, type = Type.U63)
- public final long txBytes;
- @Field(order = 5, type = Type.U63)
- public final long txErrors;
-
- public TetherStatsValue(final long rxPackets, final long rxBytes, final long rxErrors,
- final long txPackets, final long txBytes, final long txErrors) {
- this.rxPackets = rxPackets;
- this.rxBytes = rxBytes;
- this.rxErrors = rxErrors;
- this.txPackets = txPackets;
- this.txBytes = txBytes;
- this.txErrors = txErrors;
- }
-
- // TODO: remove equals, hashCode and toString once aosp/1536721 is merged.
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
-
- if (!(obj instanceof TetherStatsValue)) return false;
-
- final TetherStatsValue that = (TetherStatsValue) obj;
-
- return rxPackets == that.rxPackets
- && rxBytes == that.rxBytes
- && rxErrors == that.rxErrors
- && txPackets == that.txPackets
- && txBytes == that.txBytes
- && txErrors == that.txErrors;
- }
-
- @Override
- public int hashCode() {
- return Long.hashCode(rxPackets) ^ Long.hashCode(rxBytes) ^ Long.hashCode(rxErrors)
- ^ Long.hashCode(txPackets) ^ Long.hashCode(txBytes) ^ Long.hashCode(txErrors);
- }
-
- @Override
- public String toString() {
- return String.format("rxPackets: %s, rxBytes: %s, rxErrors: %s, txPackets: %s, "
- + "txBytes: %s, txErrors: %s", rxPackets, rxBytes, rxErrors, txPackets,
- txBytes, txErrors);
- }
-}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 0b607bd..4504829 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -135,6 +135,7 @@
import com.android.internal.util.StateMachine;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.CollectionUtils;
import com.android.networkstack.apishim.common.BluetoothPanShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
@@ -278,6 +279,11 @@
private BluetoothPan mBluetoothPan;
private PanServiceListener mBluetoothPanListener;
private ArrayList<Pair<Boolean, IIntResultListener>> mPendingPanRequests;
+ // AIDL doesn't support Set<Integer>. Maintain a int bitmap here. When the bitmap is passed to
+ // TetheringManager, TetheringManager would convert it to a set of Integer types.
+ // mSupportedTypeBitmap should always be updated inside tethering internal thread but it may be
+ // read from binder thread which called TetheringService directly.
+ private volatile long mSupportedTypeBitmap;
public Tethering(TetheringDependencies deps) {
mLog.mark("Tethering.constructed");
@@ -494,6 +500,8 @@
mUpstreamNetworkMonitor.setUpstreamConfig(mConfig.chooseUpstreamAutomatically,
mConfig.isDunRequired);
reportConfigurationChanged(mConfig.toStableParcelable());
+
+ updateSupportedDownstreams(mConfig);
}
private void maybeDunSettingChanged() {
@@ -1513,26 +1521,6 @@
return mConfig;
}
- boolean hasAnySupportedDownstream() {
- if ((mConfig.tetherableUsbRegexs.length != 0)
- || (mConfig.tetherableWifiRegexs.length != 0)
- || (mConfig.tetherableBluetoothRegexs.length != 0)) {
- return true;
- }
-
- // Before T, isTetheringSupported would return true if wifi, usb and bluetooth tethering are
- // disabled (whole tethering settings would be hidden). This means tethering would also not
- // support wifi p2p, ethernet tethering and mirrorlink. This is wrong but probably there are
- // some devices in the field rely on this to disable tethering entirely.
- if (!SdkLevel.isAtLeastT()) return false;
-
- return (mConfig.tetherableWifiP2pRegexs.length != 0)
- || (mConfig.tetherableNcmRegexs.length != 0)
- || isEthernetSupported();
- }
-
- // TODO: using EtherentManager new API to check whether ethernet is supported when the API is
- // ready to use.
private boolean isEthernetSupported() {
return mContext.getSystemService(Context.ETHERNET_SERVICE) != null;
}
@@ -2322,7 +2310,7 @@
mHandler.post(() -> {
mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
- parcel.tetheringSupported = isTetheringSupported();
+ parcel.supportedTypes = mSupportedTypeBitmap;
parcel.upstreamNetwork = mTetherUpstream;
parcel.config = mConfig.toStableParcelable();
parcel.states =
@@ -2361,6 +2349,22 @@
});
}
+ private void reportTetheringSupportedChange(final long supportedBitmap) {
+ final int length = mTetheringEventCallbacks.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mTetheringEventCallbacks.getBroadcastItem(i).onSupportedTetheringTypes(
+ supportedBitmap);
+ } catch (RemoteException e) {
+ // Not really very much to do here.
+ }
+ }
+ } finally {
+ mTetheringEventCallbacks.finishBroadcast();
+ }
+ }
+
private void reportUpstreamChanged(UpstreamNetworkState ns) {
final int length = mTetheringEventCallbacks.beginBroadcast();
final Network network = (ns != null) ? ns.network : null;
@@ -2445,18 +2449,56 @@
}
}
+ private void updateSupportedDownstreams(final TetheringConfiguration config) {
+ final long preSupportedBitmap = mSupportedTypeBitmap;
+
+ if (!isTetheringAllowed() || mEntitlementMgr.isProvisioningNeededButUnavailable()) {
+ mSupportedTypeBitmap = 0;
+ } else {
+ mSupportedTypeBitmap = makeSupportedDownstreams(config);
+ }
+
+ if (preSupportedBitmap != mSupportedTypeBitmap) {
+ reportTetheringSupportedChange(mSupportedTypeBitmap);
+ }
+ }
+
+ private long makeSupportedDownstreams(final TetheringConfiguration config) {
+ long types = 0;
+ if (config.tetherableUsbRegexs.length != 0) types |= (1 << TETHERING_USB);
+
+ if (config.tetherableWifiRegexs.length != 0) types |= (1 << TETHERING_WIFI);
+
+ if (config.tetherableBluetoothRegexs.length != 0) types |= (1 << TETHERING_BLUETOOTH);
+
+ // Before T, isTetheringSupported would return true if wifi, usb and bluetooth tethering are
+ // disabled (whole tethering settings would be hidden). This means tethering would also not
+ // support wifi p2p, ethernet tethering and mirrorlink. This is wrong but probably there are
+ // some devices in the field rely on this to disable tethering entirely.
+ if (!SdkLevel.isAtLeastT() && types == 0) return types;
+
+ if (config.tetherableNcmRegexs.length != 0) types |= (1 << TETHERING_NCM);
+
+ if (config.tetherableWifiP2pRegexs.length != 0) types |= (1 << TETHERING_WIFI_P2P);
+
+ if (isEthernetSupported()) types |= (1 << TETHERING_ETHERNET);
+
+ return types;
+ }
+
// if ro.tether.denied = true we default to no tethering
// gservices could set the secure setting to 1 though to enable it on a build where it
// had previously been turned off.
- boolean isTetheringSupported() {
+ private boolean isTetheringAllowed() {
final int defaultVal = mDeps.isTetheringDenied() ? 0 : 1;
final boolean tetherSupported = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.TETHER_SUPPORTED, defaultVal) != 0;
- final boolean tetherEnabledInSettings = tetherSupported
+ return tetherSupported
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
+ }
- return tetherEnabledInSettings && hasAnySupportedDownstream()
- && !mEntitlementMgr.isProvisioningNeededButUnavailable();
+ boolean isTetheringSupported() {
+ return mSupportedTypeBitmap > 0;
}
private void dumpBpf(IndentingPrintWriter pw) {
@@ -2472,13 +2514,12 @@
writer, " ");
// Used for testing instead of human debug.
- // TODO: add options to choose which map to dump.
- if (argsContain(args, "bpfRawMap")) {
- mBpfCoordinator.dumpRawMap(pw);
+ if (CollectionUtils.contains(args, "bpfRawMap")) {
+ mBpfCoordinator.dumpRawMap(pw, args);
return;
}
- if (argsContain(args, "bpf")) {
+ if (CollectionUtils.contains(args, "bpf")) {
dumpBpf(pw);
return;
}
@@ -2544,7 +2585,7 @@
pw.println("Log:");
pw.increaseIndent();
- if (argsContain(args, "--short")) {
+ if (CollectionUtils.contains(args, "--short")) {
pw.println("<log removed for brevity>");
} else {
mLog.dump(fd, pw, args);
@@ -2588,13 +2629,6 @@
if (e != null) throw e;
}
- private static boolean argsContain(String[] args, String target) {
- for (String arg : args) {
- if (target.equals(arg)) return true;
- }
- return false;
- }
-
private void updateConnectedClients(final List<WifiClient> wifiClients) {
if (mConnectedClientsTracker.updateConnectedClients(mTetherMainSM.getAllDownstreams(),
wifiClients)) {
diff --git a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
index 66d67a1..e6236df 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
@@ -22,7 +22,7 @@
import androidx.annotation.NonNull;
import com.android.net.module.util.JniUtil;
-import com.android.networkstack.tethering.TetherStatsValue;
+import com.android.net.module.util.bpf.TetherStatsValue;
import java.io.FileDescriptor;
import java.net.Inet6Address;
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index de81a38..e73b7d5 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -74,6 +74,8 @@
import com.android.net.module.util.Struct;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Icmpv6Header;
import com.android.net.module.util.structs.Ipv4Header;
@@ -128,6 +130,13 @@
// Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
// See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
private static final int UDP_STREAM_TS_MS = 2000;
+ // Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
+ private static final int RX_UDP_PACKET_SIZE = 30;
+ private static final int RX_UDP_PACKET_COUNT = 456;
+ // Per TX UDP packet size: ethhdr (14) + iphdr (20) + udphdr (8) + payload (2) = 44 bytes.
+ private static final int TX_UDP_PACKET_SIZE = 44;
+ private static final int TX_UDP_PACKET_COUNT = 123;
+
private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/8");
private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
@@ -136,6 +145,8 @@
ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
+ private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
+ private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
private static final String BASE64_DELIMITER = ",";
private static final String LINE_DELIMITER = "\\n";
@@ -168,12 +179,13 @@
mUiAutomation.adoptShellPermissionIdentity(
MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED, ACCESS_NETWORK_STATE,
CONNECTIVITY_USE_RESTRICTED_NETWORKS, DUMP);
- mRunTests = mTm.isTetheringSupported() && mEm != null;
- assumeTrue(mRunTests);
-
mHandlerThread = new HandlerThread(getClass().getSimpleName());
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+
+ mRunTests = isEthernetTetheringSupported();
+ assumeTrue(mRunTests);
+
mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm);
}
@@ -201,7 +213,6 @@
mHandler.post(() -> reader.stop());
mDownstreamReader = null;
}
- mHandlerThread.quitSafely();
mTetheredInterfaceRequester.release();
mEm.setIncludeTestInterfaces(false);
maybeDeleteTestInterface();
@@ -212,6 +223,7 @@
try {
if (mRunTests) cleanUp();
} finally {
+ mHandlerThread.quitSafely();
mUiAutomation.dropShellPermissionIdentity();
}
}
@@ -400,6 +412,23 @@
// client, which is not possible in this test.
}
+ private boolean isEthernetTetheringSupported() throws Exception {
+ final CompletableFuture<Boolean> future = new CompletableFuture<>();
+ final TetheringEventCallback callback = new TetheringEventCallback() {
+ @Override
+ public void onSupportedTetheringTypes(Set<Integer> supportedTypes) {
+ future.complete(supportedTypes.contains(TETHERING_ETHERNET));
+ }
+ };
+
+ try {
+ mTm.registerTetheringEventCallback(mHandler::post, callback);
+ return future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } finally {
+ mTm.unregisterTetheringEventCallback(callback);
+ }
+ }
+
private static final class MyTetheringEventCallback implements TetheringEventCallback {
private final TetheringManager mTm;
private final CountDownLatch mTetheringStartedLatch = new CountDownLatch(1);
@@ -938,27 +967,69 @@
return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD3);
});
- final HashMap<Tether4Key, Tether4Value> upstreamMap = pollIpv4UpstreamMapFromDump();
+ // [1] Verify IPv4 upstream rule map.
+ final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
+ Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
assertNotNull(upstreamMap);
assertEquals(1, upstreamMap.size());
final Map.Entry<Tether4Key, Tether4Value> rule =
upstreamMap.entrySet().iterator().next();
- final Tether4Key key = rule.getKey();
- assertEquals(IPPROTO_UDP, key.l4proto);
- assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), key.src4));
- assertEquals(LOCAL_PORT, key.srcPort);
- assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), key.dst4));
- assertEquals(REMOTE_PORT, key.dstPort);
+ final Tether4Key upstream4Key = rule.getKey();
+ assertEquals(IPPROTO_UDP, upstream4Key.l4proto);
+ assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), upstream4Key.src4));
+ assertEquals(LOCAL_PORT, upstream4Key.srcPort);
+ assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), upstream4Key.dst4));
+ assertEquals(REMOTE_PORT, upstream4Key.dstPort);
- final Tether4Value value = rule.getValue();
+ final Tether4Value upstream4Value = rule.getValue();
assertTrue(Arrays.equals(publicIp4Addr.getAddress(),
- InetAddress.getByAddress(value.src46).getAddress()));
- assertEquals(LOCAL_PORT, value.srcPort);
+ InetAddress.getByAddress(upstream4Value.src46).getAddress()));
+ assertEquals(LOCAL_PORT, upstream4Value.srcPort);
assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(),
- InetAddress.getByAddress(value.dst46).getAddress()));
- assertEquals(REMOTE_PORT, value.dstPort);
+ InetAddress.getByAddress(upstream4Value.dst46).getAddress()));
+ assertEquals(REMOTE_PORT, upstream4Value.dstPort);
+
+ // [2] Verify stats map.
+ // Transmit packets on both direction for verifying stats. Because we only care the
+ // packet count in stats test, we just reuse the existing packets to increaes
+ // the packet count on both direction.
+
+ // Send packets on original direction.
+ for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
+ tester.verifyUpload(remote, originalPacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD);
+ });
+ }
+
+ // Send packets on reply direction.
+ for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
+ remote.verifyDownload(tester, replyPacket, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, true/* hasEther */, PAYLOAD2);
+ });
+ }
+
+ // Dump stats map to verify.
+ final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump(
+ TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS);
+ assertNotNull(statsMap);
+ assertEquals(1, statsMap.size());
+
+ final Map.Entry<TetherStatsKey, TetherStatsValue> stats =
+ statsMap.entrySet().iterator().next();
+
+ // TODO: verify the upstream index in TetherStatsKey.
+
+ final TetherStatsValue statsValue = stats.getValue();
+ assertEquals(RX_UDP_PACKET_COUNT, statsValue.rxPackets);
+ assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE, statsValue.rxBytes);
+ assertEquals(0, statsValue.rxErrors);
+ assertEquals(TX_UDP_PACKET_COUNT, statsValue.txPackets);
+ assertEquals(TX_UDP_PACKET_COUNT * TX_UDP_PACKET_SIZE, statsValue.txBytes);
+ assertEquals(0, statsValue.txErrors);
}
}
@@ -1003,7 +1074,8 @@
}
@Nullable
- private Pair<Tether4Key, Tether4Value> parseTether4KeyValue(@NonNull String dumpStr) {
+ private <K extends Struct, V extends Struct> Pair<K, V> parseMapKeyValue(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String dumpStr) {
Log.w(TAG, "Parsing string: " + dumpStr);
String[] keyValueStrs = dumpStr.split(BASE64_DELIMITER);
@@ -1016,36 +1088,38 @@
Log.d(TAG, "keyBytes: " + dumpHexString(keyBytes));
final ByteBuffer keyByteBuffer = ByteBuffer.wrap(keyBytes);
keyByteBuffer.order(ByteOrder.nativeOrder());
- final Tether4Key tether4Key = Struct.parse(Tether4Key.class, keyByteBuffer);
- Log.w(TAG, "tether4Key: " + tether4Key);
+ final K k = Struct.parse(keyClass, keyByteBuffer);
final byte[] valueBytes = Base64.decode(keyValueStrs[1], Base64.DEFAULT);
Log.d(TAG, "valueBytes: " + dumpHexString(valueBytes));
final ByteBuffer valueByteBuffer = ByteBuffer.wrap(valueBytes);
valueByteBuffer.order(ByteOrder.nativeOrder());
- final Tether4Value tether4Value = Struct.parse(Tether4Value.class, valueByteBuffer);
- Log.w(TAG, "tether4Value: " + tether4Value);
+ final V v = Struct.parse(valueClass, valueByteBuffer);
- return new Pair<>(tether4Key, tether4Value);
+ return new Pair<>(k, v);
}
@NonNull
- private HashMap<Tether4Key, Tether4Value> dumpIpv4UpstreamMap() throws Exception {
- final String rawMapStr = DumpTestUtils.dumpService(Context.TETHERING_SERVICE,
- DUMPSYS_TETHERING_RAWMAP_ARG);
- final HashMap<Tether4Key, Tether4Value> map = new HashMap<>();
+ private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+ throws Exception {
+ final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
+ final String rawMapStr = DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args);
+ final HashMap<K, V> map = new HashMap<>();
for (final String line : rawMapStr.split(LINE_DELIMITER)) {
- final Pair<Tether4Key, Tether4Value> rule = parseTether4KeyValue(line.trim());
+ final Pair<K, V> rule = parseMapKeyValue(keyClass, valueClass, line.trim());
map.put(rule.first, rule.second);
}
return map;
}
@Nullable
- private HashMap<Tether4Key, Tether4Value> pollIpv4UpstreamMapFromDump() throws Exception {
+ private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+ throws Exception {
for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
- final HashMap<Tether4Key, Tether4Value> map = dumpIpv4UpstreamMap();
+ final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
if (!map.isEmpty()) return map;
Thread.sleep(DUMP_POLLING_INTERVAL_MS);
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 43f1eaa..aac531a 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -103,6 +103,8 @@
import com.android.net.module.util.NetworkStackConstants;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
@@ -112,8 +114,6 @@
import com.android.networkstack.tethering.TetherDownstream6Key;
import com.android.networkstack.tethering.TetherLimitKey;
import com.android.networkstack.tethering.TetherLimitValue;
-import com.android.networkstack.tethering.TetherStatsKey;
-import com.android.networkstack.tethering.TetherStatsValue;
import com.android.networkstack.tethering.TetherUpstream6Key;
import com.android.networkstack.tethering.TetheringConfiguration;
import com.android.networkstack.tethering.util.InterfaceSet;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 4967d27..3630f24 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -102,6 +102,8 @@
import com.android.net.module.util.NetworkStackConstants;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.net.module.util.netlink.ConntrackMessage;
import com.android.net.module.util.netlink.NetlinkConstants;
import com.android.net.module.util.netlink.NetlinkSocket;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 0388758..2fd7f48 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -142,6 +142,7 @@
import android.net.TetheringCallbackStartedParcel;
import android.net.TetheringConfigurationParcel;
import android.net.TetheringInterface;
+import android.net.TetheringManager;
import android.net.TetheringRequestParcel;
import android.net.dhcp.DhcpLeaseParcelable;
import android.net.dhcp.DhcpServerCallbacks;
@@ -175,6 +176,7 @@
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
+import android.util.ArraySet;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
@@ -211,6 +213,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.Vector;
@RunWith(AndroidJUnit4.class)
@@ -1696,6 +1699,7 @@
private final ArrayList<TetherStatesParcel> mTetherStates = new ArrayList<>();
private final ArrayList<Integer> mOffloadStatus = new ArrayList<>();
private final ArrayList<List<TetheredClient>> mTetheredClients = new ArrayList<>();
+ private final ArrayList<Long> mSupportedBitmaps = new ArrayList<>();
// This function will remove the recorded callbacks, so it must be called once for
// each callback. If this is called after multiple callback, the order matters.
@@ -1748,6 +1752,10 @@
assertTrue(leases.containsAll(result));
}
+ public void expectSupportedTetheringTypes(Set<Integer> expectedTypes) {
+ assertEquals(expectedTypes, TetheringManager.unpackBits(mSupportedBitmaps.remove(0)));
+ }
+
@Override
public void onUpstreamChanged(Network network) {
mActualUpstreams.add(network);
@@ -1780,11 +1788,17 @@
mTetherStates.add(parcel.states);
mOffloadStatus.add(parcel.offloadStatus);
mTetheredClients.add(parcel.tetheredClients);
+ mSupportedBitmaps.add(parcel.supportedTypes);
}
@Override
public void onCallbackStopped(int errorCode) { }
+ @Override
+ public void onSupportedTetheringTypes(long supportedBitmap) {
+ mSupportedBitmaps.add(supportedBitmap);
+ }
+
public void assertNoUpstreamChangeCallback() {
assertTrue(mActualUpstreams.isEmpty());
}
@@ -2836,53 +2850,81 @@
runStopUSBTethering();
}
+ public static ArraySet<Integer> getAllSupportedTetheringTypes() {
+ return new ArraySet<>(new Integer[] { TETHERING_USB, TETHERING_NCM, TETHERING_WIFI,
+ TETHERING_WIFI_P2P, TETHERING_BLUETOOTH, TETHERING_ETHERNET });
+ }
+
@Test
public void testTetheringSupported() throws Exception {
+ final ArraySet<Integer> expectedTypes = getAllSupportedTetheringTypes();
+ // Check tethering is supported after initialization.
setTetheringSupported(true /* supported */);
- updateConfigAndVerifySupported(true /* supported */);
+ TestTetheringEventCallback callback = new TestTetheringEventCallback();
+ mTethering.registerTetheringEventCallback(callback);
+ mLooper.dispatchAll();
+ updateConfigAndVerifySupported(callback, expectedTypes);
// Could disable tethering supported by settings.
Settings.Global.putInt(mContentResolver, Settings.Global.TETHER_SUPPORTED, 0);
- updateConfigAndVerifySupported(false /* supported */);
+ updateConfigAndVerifySupported(callback, new ArraySet<>());
// Could disable tethering supported by user restriction.
setTetheringSupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
when(mUserManager.hasUserRestriction(
UserManager.DISALLOW_CONFIG_TETHERING)).thenReturn(true);
- updateConfigAndVerifySupported(false /* supported */);
+ updateConfigAndVerifySupported(callback, new ArraySet<>());
// Tethering is supported if it has any supported downstream.
setTetheringSupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Usb tethering is not supported:
+ expectedTypes.remove(TETHERING_USB);
when(mResources.getStringArray(R.array.config_tether_usb_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Wifi tethering is not supported:
+ expectedTypes.remove(TETHERING_WIFI);
when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
-
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Bluetooth tethering is not supported:
+ expectedTypes.remove(TETHERING_BLUETOOTH);
+ when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
+ .thenReturn(new String[0]);
if (isAtLeastT()) {
- when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
- .thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+
+ // P2p tethering is not supported:
+ expectedTypes.remove(TETHERING_WIFI_P2P);
when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Ncm tethering is not supported:
+ expectedTypes.remove(TETHERING_NCM);
when(mResources.getStringArray(R.array.config_tether_ncm_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(true /* supported */);
+ updateConfigAndVerifySupported(callback, expectedTypes);
+ // Ethernet tethering (last supported type) is not supported:
+ expectedTypes.remove(TETHERING_ETHERNET);
mForceEthernetServiceUnavailable = true;
- updateConfigAndVerifySupported(false /* supported */);
+ updateConfigAndVerifySupported(callback, new ArraySet<>());
+
} else {
- when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
- .thenReturn(new String[0]);
- updateConfigAndVerifySupported(false /* supported */);
+ // If wifi, usb and bluetooth are all not supported, all the types are not supported.
+ expectedTypes.clear();
+ updateConfigAndVerifySupported(callback, expectedTypes);
}
}
- private void updateConfigAndVerifySupported(boolean supported) {
+ private void updateConfigAndVerifySupported(final TestTetheringEventCallback callback,
+ final ArraySet<Integer> expectedTypes) {
sendConfigurationChanged();
- assertEquals(supported, mTethering.isTetheringSupported());
+
+ assertEquals(expectedTypes.size() > 0, mTethering.isTetheringSupported());
+ callback.expectSupportedTetheringTypes(expectedTypes);
}
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index c798580..e382713 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -65,8 +65,9 @@
skb->pkt_type == PACKET_MULTICAST;
}
-// try to make the first 'len' header bytes readable via direct packet access
-static inline __always_inline void try_make_readable(struct __sk_buff* skb, int len) {
+// try to make the first 'len' header bytes readable/writable via direct packet access
+// (note: AFAIK there is no way to ask for only direct packet read without also getting write)
+static inline __always_inline void try_make_writable(struct __sk_buff* skb, int len) {
if (len > skb->len) len = skb->len;
if (skb->data_end - skb->data < len) bpf_skb_pull_data(skb, len);
}
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index 8971f6b..9a9d337 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -57,7 +57,7 @@
// Not clear if this is actually necessary considering we use DPA (Direct Packet Access),
// but we need to make sure we can read the IPv6 header reliably so that we can set
// skb->mark = 0xDeadC1a7 for packets we fail to offload.
- try_make_readable(skb, l2_header_size + sizeof(struct ipv6hdr));
+ try_make_writable(skb, l2_header_size + sizeof(struct ipv6hdr));
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
@@ -224,7 +224,7 @@
if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
// Possibly not needed, but for consistency with nat64 up above
- try_make_readable(skb, sizeof(struct iphdr));
+ try_make_writable(skb, sizeof(struct iphdr));
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
diff --git a/bpf_progs/dscp_policy.c b/bpf_progs/dscp_policy.c
index 9989e6b..d5df7ef 100644
--- a/bpf_progs/dscp_policy.c
+++ b/bpf_progs/dscp_policy.c
@@ -16,6 +16,7 @@
#include <linux/types.h>
#include <linux/bpf.h>
+#include <linux/if_packet.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/if_ether.h>
@@ -27,249 +28,294 @@
#include <string.h>
#include "bpf_helpers.h"
+#include "dscp_policy.h"
-#define MAX_POLICIES 16
-#define MAP_A 1
-#define MAP_B 2
-
-#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
-
-// TODO: these are already defined in /system/netd/bpf_progs/bpf_net_helpers.h
-// should they be moved to common location?
-static uint64_t (*bpf_get_socket_cookie)(struct __sk_buff* skb) =
- (void*)BPF_FUNC_get_socket_cookie;
-static int (*bpf_skb_store_bytes)(struct __sk_buff* skb, __u32 offset, const void* from, __u32 len,
- __u64 flags) = (void*)BPF_FUNC_skb_store_bytes;
-static int (*bpf_l3_csum_replace)(struct __sk_buff* skb, __u32 offset, __u64 from, __u64 to,
- __u64 flags) = (void*)BPF_FUNC_l3_csum_replace;
-
-typedef struct {
- // Add family here to match __sk_buff ?
- struct in_addr srcIp;
- struct in_addr dstIp;
- __be16 srcPort;
- __be16 dstPort;
- uint8_t proto;
- uint8_t dscpVal;
- uint8_t pad[2];
-} Ipv4RuleEntry;
-STRUCT_SIZE(Ipv4RuleEntry, 2 * 4 + 2 * 2 + 2 * 1 + 2); // 16, 4 for in_addr
-
-#define SRC_IP_MASK 1
-#define DST_IP_MASK 2
-#define SRC_PORT_MASK 4
-#define DST_PORT_MASK 8
-#define PROTO_MASK 16
-
-typedef struct {
- struct in6_addr srcIp;
- struct in6_addr dstIp;
- __be16 srcPort;
- __be16 dstPortStart;
- __be16 dstPortEnd;
- uint8_t proto;
- uint8_t dscpVal;
- uint8_t mask;
- uint8_t pad[3];
-} Ipv4Policy;
-STRUCT_SIZE(Ipv4Policy, 2 * 16 + 3 * 2 + 3 * 1 + 3); // 44
-
-typedef struct {
- struct in6_addr srcIp;
- struct in6_addr dstIp;
- __be16 srcPort;
- __be16 dstPortStart;
- __be16 dstPortEnd;
- uint8_t proto;
- uint8_t dscpVal;
- uint8_t mask;
- // should we override this struct to include the param bitmask for linear search?
- // For mapping socket to policies, all the params should match exactly since we can
- // pull any missing from the sock itself.
-} Ipv6RuleEntry;
-STRUCT_SIZE(Ipv6RuleEntry, 2 * 16 + 3 * 2 + 3 * 1 + 3); // 44
-
-// TODO: move to using 1 map. Map v4 address to 0xffff::v4
-DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_A, HASH, uint64_t, Ipv4RuleEntry, MAX_POLICIES,
- AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_B, HASH, uint64_t, Ipv4RuleEntry, MAX_POLICIES,
- AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_A, HASH, uint64_t, Ipv6RuleEntry, MAX_POLICIES,
- AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_B, HASH, uint64_t, Ipv6RuleEntry, MAX_POLICIES,
- AID_SYSTEM)
DEFINE_BPF_MAP_GRW(switch_comp_map, ARRAY, int, uint64_t, 1, AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv4_dscp_policies_map, ARRAY, uint32_t, Ipv4Policy, MAX_POLICIES,
+DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_A, HASH, uint64_t, RuleEntry, MAX_POLICIES,
AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv6_dscp_policies_map, ARRAY, uint32_t, Ipv6RuleEntry, MAX_POLICIES,
+DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_B, HASH, uint64_t, RuleEntry, MAX_POLICIES,
+ AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_A, HASH, uint64_t, RuleEntry, MAX_POLICIES,
+ AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_B, HASH, uint64_t, RuleEntry, MAX_POLICIES,
AID_SYSTEM)
-DEFINE_BPF_PROG_KVER("schedcls/set_dscp", AID_ROOT, AID_SYSTEM,
- schedcls_set_dscp, KVER(5, 4, 0))
-(struct __sk_buff* skb) {
- int one = 0;
- uint64_t* selectedMap = bpf_switch_comp_map_lookup_elem(&one);
+DEFINE_BPF_MAP_GRW(ipv4_dscp_policies_map, ARRAY, uint32_t, DscpPolicy, MAX_POLICIES,
+ AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(ipv6_dscp_policies_map, ARRAY, uint32_t, DscpPolicy, MAX_POLICIES,
+ AID_SYSTEM)
+
+static inline __always_inline void match_policy(struct __sk_buff* skb, bool ipv4, bool is_eth) {
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+
+ const int l2_header_size = is_eth ? sizeof(struct ethhdr) : 0;
+ struct ethhdr* eth = is_eth ? data : NULL;
+
+ if (data + l2_header_size > data_end) return;
+
+ int zero = 0;
+ int hdr_size = 0;
+ uint64_t* selectedMap = bpf_switch_comp_map_lookup_elem(&zero);
// use this with HASH map so map lookup only happens once policies have been added?
if (!selectedMap) {
- return TC_ACT_PIPE;
+ return;
}
// used for map lookup
uint64_t cookie = bpf_get_socket_cookie(skb);
+ if (!cookie)
+ return;
- // Do we need separate maps for ipv4/ipv6
- if (skb->protocol == htons(ETH_P_IP)) { //maybe bpf_htons()
- Ipv4RuleEntry* v4Policy;
- if (*selectedMap == MAP_A) {
- v4Policy = bpf_ipv4_socket_to_policies_map_A_lookup_elem(&cookie);
- } else {
- v4Policy = bpf_ipv4_socket_to_policies_map_B_lookup_elem(&cookie);
- }
-
- // How to use bitmask here to compare params efficiently?
- // TODO: add BPF_PROG_TYPE_SK_SKB prog type to Loader?
-
- void* data = (void*)(long)skb->data;
- const void* data_end = (void*)(long)skb->data_end;
- const struct iphdr* const iph = data;
-
+ uint16_t sport = 0;
+ uint16_t dport = 0;
+ uint8_t protocol = 0; // TODO: Use are reserved value? Or int (-1) and cast to uint below?
+ struct in6_addr srcIp = {};
+ struct in6_addr dstIp = {};
+ uint8_t tos = 0; // Only used for IPv4
+ uint8_t priority = 0; // Only used for IPv6
+ uint8_t flow_lbl = 0; // Only used for IPv6
+ if (ipv4) {
+ const struct iphdr* const iph = is_eth ? (void*)(eth + 1) : data;
// Must have ipv4 header
- if (data + sizeof(*iph) > data_end) return TC_ACT_PIPE;
+ if (data + l2_header_size + sizeof(*iph) > data_end) return;
// IP version must be 4
- if (iph->version != 4) return TC_ACT_PIPE;
+ if (iph->version != 4) return;
// We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
- if (iph->ihl != 5) return TC_ACT_PIPE;
+ if (iph->ihl != 5) return;
- if (iph->protocol != IPPROTO_UDP) return TC_ACT_PIPE;
+ // V4 mapped address in in6_addr sets 10/11 position to 0xff.
+ srcIp.s6_addr32[2] = htonl(0x0000ffff);
+ dstIp.s6_addr32[2] = htonl(0x0000ffff);
- struct udphdr *udp;
- udp = data + sizeof(struct iphdr); //sizeof(struct ethhdr)
+ // Copy IPv4 address into in6_addr for easy comparison below.
+ srcIp.s6_addr32[3] = iph->saddr;
+ dstIp.s6_addr32[3] = iph->daddr;
+ protocol = iph->protocol;
+ tos = iph->tos;
+ hdr_size = sizeof(struct iphdr);
+ } else {
+ struct ipv6hdr* ip6h = is_eth ? (void*)(eth + 1) : data;
+ // Must have ipv6 header
+ if (data + l2_header_size + sizeof(*ip6h) > data_end) return;
- if ((void*)(udp + 1) > data_end) return TC_ACT_PIPE;
+ if (ip6h->version != 6) return;
- // Source/destination port in udphdr are stored in be16, need to convert to le16.
- // This can be done via ntohs or htons. Is there a more preferred way?
- // Cached policy was found.
- if (v4Policy && iph->saddr == v4Policy->srcIp.s_addr &&
- iph->daddr == v4Policy->dstIp.s_addr &&
- ntohs(udp->source) == v4Policy->srcPort &&
- ntohs(udp->dest) == v4Policy->dstPort &&
- iph->protocol == v4Policy->proto) {
- // set dscpVal in packet. Least sig 2 bits of TOS
- // reference ipv4_change_dsfield()
+ srcIp = ip6h->saddr;
+ dstIp = ip6h->daddr;
+ protocol = ip6h->nexthdr;
+ priority = ip6h->priority;
+ flow_lbl = ip6h->flow_lbl[0];
+ hdr_size = sizeof(struct ipv6hdr);
+ }
- // TODO: fix checksum...
- int ecn = iph->tos & 3;
- uint8_t newDscpVal = (v4Policy->dscpVal << 2) + ecn;
- int oldDscpVal = iph->tos >> 2;
+ switch (protocol) {
+ case IPPROTO_UDP:
+ case IPPROTO_UDPLITE:
+ {
+ struct udphdr *udp;
+ udp = data + hdr_size;
+ if ((void*)(udp + 1) > data_end) return;
+ sport = udp->source;
+ dport = udp->dest;
+ }
+ break;
+ case IPPROTO_TCP:
+ {
+ struct tcphdr *tcp;
+ tcp = data + hdr_size;
+ if ((void*)(tcp + 1) > data_end) return;
+ sport = tcp->source;
+ dport = tcp->dest;
+ }
+ break;
+ default:
+ return;
+ }
+
+ RuleEntry* existingRule;
+ if (ipv4) {
+ if (*selectedMap == MAP_A) {
+ existingRule = bpf_ipv4_socket_to_policies_map_A_lookup_elem(&cookie);
+ } else {
+ existingRule = bpf_ipv4_socket_to_policies_map_B_lookup_elem(&cookie);
+ }
+ } else {
+ if (*selectedMap == MAP_A) {
+ existingRule = bpf_ipv6_socket_to_policies_map_A_lookup_elem(&cookie);
+ } else {
+ existingRule = bpf_ipv6_socket_to_policies_map_B_lookup_elem(&cookie);
+ }
+ }
+
+ if (existingRule && v6_equal(srcIp, existingRule->srcIp) &&
+ v6_equal(dstIp, existingRule->dstIp) &&
+ skb->ifindex == existingRule->ifindex &&
+ ntohs(sport) == htons(existingRule->srcPort) &&
+ ntohs(dport) == htons(existingRule->dstPort) &&
+ protocol == existingRule->proto) {
+ if (ipv4) {
+ int ecn = tos & 3;
+ uint8_t newDscpVal = (existingRule->dscpVal << 2) + ecn;
+ int oldDscpVal = tos >> 2;
bpf_l3_csum_replace(skb, 1, oldDscpVal, newDscpVal, sizeof(uint8_t));
bpf_skb_store_bytes(skb, 1, &newDscpVal, sizeof(uint8_t), 0);
- return TC_ACT_PIPE;
+ } else {
+ uint8_t new_priority = (existingRule->dscpVal >> 2) + 0x60;
+ uint8_t new_flow_label = ((existingRule->dscpVal & 0xf) << 6) + (priority >> 6);
+ bpf_skb_store_bytes(skb, 0, &new_priority, sizeof(uint8_t), 0);
+ bpf_skb_store_bytes(skb, 1, &new_flow_label, sizeof(uint8_t), 0);
+ }
+ return;
+ }
+
+ // Linear scan ipv4_dscp_policies_map since no stored params match skb.
+ int bestScore = -1;
+ uint32_t bestMatch = 0;
+
+ for (register uint64_t i = 0; i < MAX_POLICIES; i++) {
+ int score = 0;
+ uint8_t tempMask = 0;
+ // Using a uint64 in for loop prevents infinite loop during BPF load,
+ // but the key is uint32, so convert back.
+ uint32_t key = i;
+
+ DscpPolicy* policy;
+ if (ipv4) {
+ policy = bpf_ipv4_dscp_policies_map_lookup_elem(&key);
+ } else {
+ policy = bpf_ipv6_dscp_policies_map_lookup_elem(&key);
}
- // linear scan ipv4_dscp_policies_map, stored socket params do not match actual
- int bestScore = -1;
- uint32_t bestMatch = 0;
+ // If the policy lookup failed, presentFields is 0, or iface index does not match
+ // index on skb buff, then we can continue to next policy.
+ if (!policy || policy->presentFields == 0 || policy->ifindex != skb->ifindex)
+ continue;
- for (register uint64_t i = 0; i < MAX_POLICIES; i++) {
- int score = 0;
- uint8_t tempMask = 0;
- // Using a uint62 in for loop prevents infinite loop during BPF load,
- // but the key is uint32, so convert back.
- uint32_t key = i;
- Ipv4Policy* policy = bpf_ipv4_dscp_policies_map_lookup_elem(&key);
+ if ((policy->presentFields & SRC_IP_MASK_FLAG) == SRC_IP_MASK_FLAG &&
+ v6_equal(srcIp, policy->srcIp)) {
+ score++;
+ tempMask |= SRC_IP_MASK_FLAG;
+ }
+ if ((policy->presentFields & DST_IP_MASK_FLAG) == DST_IP_MASK_FLAG &&
+ v6_equal(dstIp, policy->dstIp)) {
+ score++;
+ tempMask |= DST_IP_MASK_FLAG;
+ }
+ if ((policy->presentFields & SRC_PORT_MASK_FLAG) == SRC_PORT_MASK_FLAG &&
+ ntohs(sport) == htons(policy->srcPort)) {
+ score++;
+ tempMask |= SRC_PORT_MASK_FLAG;
+ }
+ if ((policy->presentFields & DST_PORT_MASK_FLAG) == DST_PORT_MASK_FLAG &&
+ ntohs(dport) >= htons(policy->dstPortStart) &&
+ ntohs(dport) <= htons(policy->dstPortEnd)) {
+ score++;
+ tempMask |= DST_PORT_MASK_FLAG;
+ }
+ if ((policy->presentFields & PROTO_MASK_FLAG) == PROTO_MASK_FLAG &&
+ protocol == policy->proto) {
+ score++;
+ tempMask |= PROTO_MASK_FLAG;
+ }
- // if mask is 0 continue, key does not have corresponding policy value
- if (policy && policy->mask != 0) {
- if ((policy->mask & SRC_IP_MASK) == SRC_IP_MASK &&
- iph->saddr == policy->srcIp.s6_addr32[3]) {
- score++;
- tempMask |= SRC_IP_MASK;
- }
- if ((policy->mask & DST_IP_MASK) == DST_IP_MASK &&
- iph->daddr == policy->dstIp.s6_addr32[3]) {
- score++;
- tempMask |= DST_IP_MASK;
- }
- if ((policy->mask & SRC_PORT_MASK) == SRC_PORT_MASK &&
- ntohs(udp->source) == htons(policy->srcPort)) {
- score++;
- tempMask |= SRC_PORT_MASK;
- }
- if ((policy->mask & DST_PORT_MASK) == DST_PORT_MASK &&
- ntohs(udp->dest) >= htons(policy->dstPortStart) &&
- ntohs(udp->dest) <= htons(policy->dstPortEnd)) {
- score++;
- tempMask |= DST_PORT_MASK;
- }
- if ((policy->mask & PROTO_MASK) == PROTO_MASK &&
- iph->protocol == policy->proto) {
- score++;
- tempMask |= PROTO_MASK;
- }
+ if (score > bestScore && tempMask == policy->presentFields) {
+ bestMatch = i;
+ bestScore = score;
+ }
+ }
- if (score > bestScore && tempMask == policy->mask) {
- bestMatch = i;
- bestScore = score;
- }
+ uint8_t new_tos= 0; // Can 0 be used as default forwarding value?
+ uint8_t new_priority = 0;
+ uint8_t new_flow_lbl = 0;
+ if (bestScore > 0) {
+ DscpPolicy* policy;
+ if (ipv4) {
+ policy = bpf_ipv4_dscp_policies_map_lookup_elem(&bestMatch);
+ } else {
+ policy = bpf_ipv6_dscp_policies_map_lookup_elem(&bestMatch);
+ }
+
+ if (policy) {
+ // TODO: if DSCP value is already set ignore?
+ if (ipv4) {
+ int ecn = tos & 3;
+ new_tos = (policy->dscpVal << 2) + ecn;
+ } else {
+ new_priority = (policy->dscpVal >> 2) + 0x60;
+ new_flow_lbl = ((policy->dscpVal & 0xf) << 6) + (flow_lbl >> 6);
+
+ // Set IPv6 curDscp value to stored value and recalulate priority
+ // and flow label during next use.
+ new_tos = policy->dscpVal;
}
}
+ } else return;
- uint8_t newDscpVal = 0; // Can 0 be used as default forwarding value?
- uint8_t curDscp = iph->tos & 252;
- if (bestScore > 0) {
- Ipv4Policy* policy = bpf_ipv4_dscp_policies_map_lookup_elem(&bestMatch);
- if (policy) {
- // TODO: if DSCP value is already set ignore?
- // TODO: update checksum, for testing increment counter...
- int ecn = iph->tos & 3;
- newDscpVal = (policy->dscpVal << 2) + ecn;
- }
- }
+ RuleEntry value = {
+ .srcIp = srcIp,
+ .dstIp = dstIp,
+ .ifindex = skb->ifindex,
+ .srcPort = sport,
+ .dstPort = dport,
+ .proto = protocol,
+ .dscpVal = new_tos,
+ };
- Ipv4RuleEntry value = {
- .srcIp.s_addr = iph->saddr,
- .dstIp.s_addr = iph->daddr,
- .srcPort = udp->source,
- .dstPort = udp->dest,
- .proto = iph->protocol,
- .dscpVal = newDscpVal,
- };
-
- if (!cookie)
- return TC_ACT_PIPE;
-
- // Update map
+ //Update map with new policy.
+ if (ipv4) {
if (*selectedMap == MAP_A) {
bpf_ipv4_socket_to_policies_map_A_update_elem(&cookie, &value, BPF_ANY);
} else {
bpf_ipv4_socket_to_policies_map_B_update_elem(&cookie, &value, BPF_ANY);
}
-
- // Need to store bytes after updating map or program will not load.
- if (newDscpVal != curDscp) {
- // 1 is the offset (Version/Header length)
- int oldDscpVal = iph->tos >> 2;
- bpf_l3_csum_replace(skb, 1, oldDscpVal, newDscpVal, sizeof(uint8_t));
- bpf_skb_store_bytes(skb, 1, &newDscpVal, sizeof(uint8_t), 0);
- }
-
- } else if (skb->protocol == htons(ETH_P_IPV6)) { //maybe bpf_htons()
- Ipv6RuleEntry* v6Policy;
+ } else {
if (*selectedMap == MAP_A) {
- v6Policy = bpf_ipv6_socket_to_policies_map_A_lookup_elem(&cookie);
+ bpf_ipv6_socket_to_policies_map_A_update_elem(&cookie, &value, BPF_ANY);
} else {
- v6Policy = bpf_ipv6_socket_to_policies_map_B_lookup_elem(&cookie);
+ bpf_ipv6_socket_to_policies_map_B_update_elem(&cookie, &value, BPF_ANY);
}
+ }
- if (!v6Policy)
- return TC_ACT_PIPE;
+ // Need to store bytes after updating map or program will not load.
+ if (ipv4 && new_tos != (tos & 252)) {
+ int oldDscpVal = tos >> 2;
+ bpf_l3_csum_replace(skb, 1, oldDscpVal, new_tos, sizeof(uint8_t));
+ bpf_skb_store_bytes(skb, 1, &new_tos, sizeof(uint8_t), 0);
+ } else if (!ipv4 && (new_priority != priority || new_flow_lbl != flow_lbl)) {
+ bpf_skb_store_bytes(skb, 0, &new_priority, sizeof(uint8_t), 0);
+ bpf_skb_store_bytes(skb, 1, &new_flow_lbl, sizeof(uint8_t), 0);
+ }
+ return;
+}
- // TODO: Add code to process IPv6 packet.
+DEFINE_BPF_PROG_KVER("schedcls/set_dscp_ether", AID_ROOT, AID_SYSTEM,
+ schedcls_set_dscp_ether, KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+
+ if (skb->pkt_type != PACKET_HOST) return TC_ACT_PIPE;
+
+ if (skb->protocol == htons(ETH_P_IP)) {
+ match_policy(skb, true, true);
+ } else if (skb->protocol == htons(ETH_P_IPV6)) {
+ match_policy(skb, false, true);
+ }
+
+ // Always return TC_ACT_PIPE
+ return TC_ACT_PIPE;
+}
+
+DEFINE_BPF_PROG_KVER("schedcls/set_dscp_raw_ip", AID_ROOT, AID_SYSTEM,
+ schedcls_set_dscp_raw_ip, KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+ if (skb->protocol == htons(ETH_P_IP)) {
+ match_policy(skb, true, false);
+ } else if (skb->protocol == htons(ETH_P_IPV6)) {
+ match_policy(skb, false, false);
}
// Always return TC_ACT_PIPE
diff --git a/bpf_progs/dscp_policy.h b/bpf_progs/dscp_policy.h
new file mode 100644
index 0000000..777c4ff
--- /dev/null
+++ b/bpf_progs/dscp_policy.h
@@ -0,0 +1,71 @@
+/*
+ * 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 MAX_POLICIES 16
+#define MAP_A 1
+#define MAP_B 2
+
+#define SRC_IP_MASK_FLAG 1
+#define DST_IP_MASK_FLAG 2
+#define SRC_PORT_MASK_FLAG 4
+#define DST_PORT_MASK_FLAG 8
+#define PROTO_MASK_FLAG 16
+
+#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
+
+#ifndef v6_equal
+#define v6_equal(a, b) (a.s6_addr32[0] == b.s6_addr32[0] && \
+ a.s6_addr32[1] == b.s6_addr32[1] && \
+ a.s6_addr32[2] == b.s6_addr32[2] && \
+ a.s6_addr32[3] == b.s6_addr32[3])
+#endif
+
+// TODO: these are already defined in packages/modules/Connectivity/bpf_progs/bpf_net_helpers.h.
+// smove to common location in future.
+static uint64_t (*bpf_get_socket_cookie)(struct __sk_buff* skb) =
+ (void*)BPF_FUNC_get_socket_cookie;
+static int (*bpf_skb_store_bytes)(struct __sk_buff* skb, __u32 offset, const void* from, __u32 len,
+ __u64 flags) = (void*)BPF_FUNC_skb_store_bytes;
+static int (*bpf_l3_csum_replace)(struct __sk_buff* skb, __u32 offset, __u64 from, __u64 to,
+ __u64 flags) = (void*)BPF_FUNC_l3_csum_replace;
+static long (*bpf_skb_ecn_set_ce)(struct __sk_buff* skb) =
+ (void*)BPF_FUNC_skb_ecn_set_ce;
+
+typedef struct {
+ struct in6_addr srcIp;
+ struct in6_addr dstIp;
+ uint32_t ifindex;
+ __be16 srcPort;
+ __be16 dstPortStart;
+ __be16 dstPortEnd;
+ uint8_t proto;
+ uint8_t dscpVal;
+ uint8_t presentFields;
+ uint8_t pad[3];
+} DscpPolicy;
+STRUCT_SIZE(DscpPolicy, 2 * 16 + 4 + 3 * 2 + 3 * 1 + 3); // 48
+
+typedef struct {
+ struct in6_addr srcIp;
+ struct in6_addr dstIp;
+ __u32 ifindex;
+ __be16 srcPort;
+ __be16 dstPort;
+ __u8 proto;
+ __u8 dscpVal;
+ __u8 pad[2];
+} RuleEntry;
+STRUCT_SIZE(RuleEntry, 2 * 16 + 1 * 4 + 2 * 2 + 2 * 1 + 2); // 44
\ No newline at end of file
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 977e918..92a774c 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -122,7 +122,7 @@
// not trigger and thus we need to manually make sure we can read packet headers via DPA.
// Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
// It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
- try_make_readable(skb, l2_header_size + IP6_HLEN + TCP_HLEN);
+ try_make_writable(skb, l2_header_size + IP6_HLEN + TCP_HLEN);
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
@@ -369,7 +369,7 @@
// not trigger and thus we need to manually make sure we can read packet headers via DPA.
// Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
// It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
- try_make_readable(skb, l2_header_size + IP4_HLEN + TCP_HLEN);
+ try_make_writable(skb, l2_header_size + IP4_HLEN + TCP_HLEN);
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
index 1b47481..eb77288 100644
--- a/framework-t/api/current.txt
+++ b/framework-t/api/current.txt
@@ -3,7 +3,7 @@
public final class NetworkStats implements java.lang.AutoCloseable {
method public void close();
- method public boolean getNextBucket(android.app.usage.NetworkStats.Bucket);
+ method public boolean getNextBucket(@Nullable android.app.usage.NetworkStats.Bucket);
method public boolean hasNextBucket();
}
@@ -40,21 +40,21 @@
}
public class NetworkStatsManager {
- method @WorkerThread public android.app.usage.NetworkStats queryDetails(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
- method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUid(int, String, long, long, int) throws java.lang.SecurityException;
- method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTag(int, String, long, long, int, int) throws java.lang.SecurityException;
- method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(int, String, long, long, int, int, int) throws java.lang.SecurityException;
- method @WorkerThread public android.app.usage.NetworkStats querySummary(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
- method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
- method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
- method public void registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback);
- method public void registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback, @Nullable android.os.Handler);
- method public void unregisterUsageCallback(android.app.usage.NetworkStatsManager.UsageCallback);
+ method @WorkerThread public android.app.usage.NetworkStats queryDetails(int, @Nullable String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+ method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUid(int, @Nullable String, long, long, int) throws java.lang.SecurityException;
+ method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTag(int, @Nullable String, long, long, int, int) throws java.lang.SecurityException;
+ method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(int, @Nullable String, long, long, int, int, int) throws java.lang.SecurityException;
+ method @WorkerThread public android.app.usage.NetworkStats querySummary(int, @Nullable String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+ method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, @Nullable String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+ method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, @Nullable String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+ method public void registerUsageCallback(int, @Nullable String, long, @NonNull android.app.usage.NetworkStatsManager.UsageCallback);
+ method public void registerUsageCallback(int, @Nullable String, long, @NonNull android.app.usage.NetworkStatsManager.UsageCallback, @Nullable android.os.Handler);
+ method public void unregisterUsageCallback(@NonNull android.app.usage.NetworkStatsManager.UsageCallback);
}
public abstract static class NetworkStatsManager.UsageCallback {
ctor public NetworkStatsManager.UsageCallback();
- method public abstract void onThresholdReached(int, String);
+ method public abstract void onThresholdReached(int, @Nullable String);
}
}
@@ -173,12 +173,12 @@
method public static void incrementOperationCount(int, int);
method public static void setThreadStatsTag(int);
method public static void setThreadStatsUid(int);
- method public static void tagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
- method public static void tagFileDescriptor(java.io.FileDescriptor) throws java.io.IOException;
- method public static void tagSocket(java.net.Socket) throws java.net.SocketException;
- method public static void untagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
- method public static void untagFileDescriptor(java.io.FileDescriptor) throws java.io.IOException;
- method public static void untagSocket(java.net.Socket) throws java.net.SocketException;
+ method public static void tagDatagramSocket(@NonNull java.net.DatagramSocket) throws java.net.SocketException;
+ method public static void tagFileDescriptor(@NonNull java.io.FileDescriptor) throws java.io.IOException;
+ method public static void tagSocket(@NonNull java.net.Socket) throws java.net.SocketException;
+ method public static void untagDatagramSocket(@NonNull java.net.DatagramSocket) throws java.net.SocketException;
+ method public static void untagFileDescriptor(@NonNull java.io.FileDescriptor) throws java.io.IOException;
+ method public static void untagSocket(@NonNull java.net.Socket) throws java.net.SocketException;
field public static final int UNSUPPORTED = -1; // 0xffffffff
}
diff --git a/framework-t/api/lint-baseline.txt b/framework-t/api/lint-baseline.txt
index 53e1beb..2996a3e 100644
--- a/framework-t/api/lint-baseline.txt
+++ b/framework-t/api/lint-baseline.txt
@@ -41,86 +41,18 @@
android.net.IpSecTransform.Builder does not declare a `build()` method, but builder classes are expected to
-MissingNullability: android.app.usage.NetworkStats#getNextBucket(android.app.usage.NetworkStats.Bucket) parameter #0:
- Missing nullability on parameter `bucketOut` in method `getNextBucket`
MissingNullability: android.app.usage.NetworkStatsManager#queryDetails(int, String, long, long):
Missing nullability on method `queryDetails` return
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetails(int, String, long, long) parameter #1:
- Missing nullability on parameter `subscriberId` in method `queryDetails`
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetailsForUid(int, String, long, long, int):
- Missing nullability on method `queryDetailsForUid` return
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetailsForUid(int, String, long, long, int) parameter #1:
- Missing nullability on parameter `subscriberId` in method `queryDetailsForUid`
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetailsForUidTag(int, String, long, long, int, int):
- Missing nullability on method `queryDetailsForUidTag` return
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetailsForUidTag(int, String, long, long, int, int) parameter #1:
- Missing nullability on parameter `subscriberId` in method `queryDetailsForUidTag`
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetailsForUidTagState(int, String, long, long, int, int, int):
- Missing nullability on method `queryDetailsForUidTagState` return
-MissingNullability: android.app.usage.NetworkStatsManager#queryDetailsForUidTagState(int, String, long, long, int, int, int) parameter #1:
- Missing nullability on parameter `subscriberId` in method `queryDetailsForUidTagState`
MissingNullability: android.app.usage.NetworkStatsManager#querySummary(int, String, long, long):
Missing nullability on method `querySummary` return
-MissingNullability: android.app.usage.NetworkStatsManager#querySummary(int, String, long, long) parameter #1:
- Missing nullability on parameter `subscriberId` in method `querySummary`
MissingNullability: android.app.usage.NetworkStatsManager#querySummaryForDevice(int, String, long, long):
Missing nullability on method `querySummaryForDevice` return
-MissingNullability: android.app.usage.NetworkStatsManager#querySummaryForDevice(int, String, long, long) parameter #1:
- Missing nullability on parameter `subscriberId` in method `querySummaryForDevice`
MissingNullability: android.app.usage.NetworkStatsManager#querySummaryForUser(int, String, long, long):
Missing nullability on method `querySummaryForUser` return
-MissingNullability: android.app.usage.NetworkStatsManager#querySummaryForUser(int, String, long, long) parameter #1:
- Missing nullability on parameter `subscriberId` in method `querySummaryForUser`
-MissingNullability: android.app.usage.NetworkStatsManager#registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback) parameter #1:
- Missing nullability on parameter `subscriberId` in method `registerUsageCallback`
-MissingNullability: android.app.usage.NetworkStatsManager#registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback) parameter #3:
- Missing nullability on parameter `callback` in method `registerUsageCallback`
-MissingNullability: android.app.usage.NetworkStatsManager#registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback, android.os.Handler) parameter #1:
- Missing nullability on parameter `subscriberId` in method `registerUsageCallback`
-MissingNullability: android.app.usage.NetworkStatsManager#registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback, android.os.Handler) parameter #3:
- Missing nullability on parameter `callback` in method `registerUsageCallback`
-MissingNullability: android.app.usage.NetworkStatsManager#unregisterUsageCallback(android.app.usage.NetworkStatsManager.UsageCallback) parameter #0:
- Missing nullability on parameter `callback` in method `unregisterUsageCallback`
-MissingNullability: android.app.usage.NetworkStatsManager.UsageCallback#onThresholdReached(int, String) parameter #1:
- Missing nullability on parameter `subscriberId` in method `onThresholdReached`
MissingNullability: android.net.IpSecAlgorithm#writeToParcel(android.os.Parcel, int) parameter #0:
Missing nullability on parameter `out` in method `writeToParcel`
MissingNullability: android.net.IpSecManager.UdpEncapsulationSocket#getFileDescriptor():
Missing nullability on method `getFileDescriptor` return
-MissingNullability: android.net.TrafficStats#tagDatagramSocket(java.net.DatagramSocket) parameter #0:
- Missing nullability on parameter `socket` in method `tagDatagramSocket`
-MissingNullability: android.net.TrafficStats#tagFileDescriptor(java.io.FileDescriptor) parameter #0:
- Missing nullability on parameter `fd` in method `tagFileDescriptor`
-MissingNullability: android.net.TrafficStats#tagSocket(java.net.Socket) parameter #0:
- Missing nullability on parameter `socket` in method `tagSocket`
-MissingNullability: android.net.TrafficStats#untagDatagramSocket(java.net.DatagramSocket) parameter #0:
- Missing nullability on parameter `socket` in method `untagDatagramSocket`
-MissingNullability: android.net.TrafficStats#untagFileDescriptor(java.io.FileDescriptor) parameter #0:
- Missing nullability on parameter `fd` in method `untagFileDescriptor`
-MissingNullability: android.net.TrafficStats#untagSocket(java.net.Socket) parameter #0:
- Missing nullability on parameter `socket` in method `untagSocket`
-MissingNullability: com.android.internal.util.FileRotator#FileRotator(java.io.File, String, long, long) parameter #0:
- Missing nullability on parameter `basePath` in method `FileRotator`
-MissingNullability: com.android.internal.util.FileRotator#FileRotator(java.io.File, String, long, long) parameter #1:
- Missing nullability on parameter `prefix` in method `FileRotator`
-MissingNullability: com.android.internal.util.FileRotator#dumpAll(java.io.OutputStream) parameter #0:
- Missing nullability on parameter `os` in method `dumpAll`
-MissingNullability: com.android.internal.util.FileRotator#readMatching(com.android.internal.util.FileRotator.Reader, long, long) parameter #0:
- Missing nullability on parameter `reader` in method `readMatching`
-MissingNullability: com.android.internal.util.FileRotator#rewriteActive(com.android.internal.util.FileRotator.Rewriter, long) parameter #0:
- Missing nullability on parameter `rewriter` in method `rewriteActive`
-MissingNullability: com.android.internal.util.FileRotator#rewriteAll(com.android.internal.util.FileRotator.Rewriter) parameter #0:
- Missing nullability on parameter `rewriter` in method `rewriteAll`
-MissingNullability: com.android.internal.util.FileRotator.Reader#read(java.io.InputStream) parameter #0:
- Missing nullability on parameter `in` in method `read`
-MissingNullability: com.android.internal.util.FileRotator.Writer#write(java.io.OutputStream) parameter #0:
- Missing nullability on parameter `out` in method `write`
-MissingNullability: com.android.server.NetworkManagementSocketTagger#kernelToTag(String) parameter #0:
- Missing nullability on parameter `string` in method `kernelToTag`
-MissingNullability: com.android.server.NetworkManagementSocketTagger#tag(java.io.FileDescriptor) parameter #0:
- Missing nullability on parameter `fd` in method `tag`
-MissingNullability: com.android.server.NetworkManagementSocketTagger#untag(java.io.FileDescriptor) parameter #0:
- Missing nullability on parameter `fd` in method `untag`
RethrowRemoteException: android.app.usage.NetworkStatsManager#queryDetails(int, String, long, long):
diff --git a/framework-t/api/module-lib-current.txt b/framework-t/api/module-lib-current.txt
index 658c625..c1f7b39 100644
--- a/framework-t/api/module-lib-current.txt
+++ b/framework-t/api/module-lib-current.txt
@@ -4,6 +4,8 @@
public class NetworkStatsManager {
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void forceUpdate();
method public static int getCollapsedRatType(int);
+ method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public android.net.NetworkStats getMobileUidStats();
+ method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public android.net.NetworkStats getWifiUidStats();
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void noteUidForeground(int, boolean);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyNetworkStatus(@NonNull java.util.List<android.net.Network>, @NonNull java.util.List<android.net.NetworkStateSnapshot>, @Nullable String, @NonNull java.util.List<android.net.UnderlyingNetworkInfo>);
method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForDevice(@NonNull android.net.NetworkTemplate, long, long);
@@ -182,6 +184,7 @@
public class TrafficStats {
method public static void attachSocketTagger();
method public static void init(@NonNull android.content.Context);
+ method public static void setThreadStatsTagDownload();
}
public final class UnderlyingNetworkInfo implements android.os.Parcelable {
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 0f37b6f..6460fed 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -2,10 +2,8 @@
package android.app.usage {
public class NetworkStatsManager {
- method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public android.net.NetworkStats getMobileUidStats();
- method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public android.net.NetworkStats getWifiUidStats();
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.NetworkStatsProvider);
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void unregisterNetworkStatsProvider(@NonNull android.net.netstats.provider.NetworkStatsProvider);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.NetworkStatsProvider);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void unregisterNetworkStatsProvider(@NonNull android.net.netstats.provider.NetworkStatsProvider);
}
}
@@ -113,7 +111,6 @@
public class TrafficStats {
method public static void setThreadStatsTagApp();
method public static void setThreadStatsTagBackup();
- method public static void setThreadStatsTagDownload();
method public static void setThreadStatsTagRestore();
field public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_END = -113; // 0xffffff8f
field public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_START = -128; // 0xffffff80
diff --git a/framework-t/src/android/app/usage/NetworkStats.java b/framework-t/src/android/app/usage/NetworkStats.java
index 2b6570a..74fe4bd 100644
--- a/framework-t/src/android/app/usage/NetworkStats.java
+++ b/framework-t/src/android/app/usage/NetworkStats.java
@@ -17,6 +17,7 @@
package android.app.usage;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.content.Context;
import android.net.INetworkStatsService;
import android.net.INetworkStatsSession;
@@ -474,10 +475,11 @@
/**
* Fills the recycled bucket with data of the next bin in the enumeration.
- * @param bucketOut Bucket to be filled with data.
+ * @param bucketOut Bucket to be filled with data. If null, the method does
+ * nothing and returning false.
* @return true if successfully filled the bucket, false otherwise.
*/
- public boolean getNextBucket(Bucket bucketOut) {
+ public boolean getNextBucket(@Nullable Bucket bucketOut) {
if (mSummary != null) {
return getNextSummaryBucket(bucketOut);
} else {
@@ -651,7 +653,7 @@
* @param bucketOut Next item will be set here.
* @return true if a next item could be set.
*/
- private boolean getNextSummaryBucket(Bucket bucketOut) {
+ private boolean getNextSummaryBucket(@Nullable Bucket bucketOut) {
if (bucketOut != null && mEnumerationIndex < mSummary.size()) {
mRecycledSummaryEntry = mSummary.getValues(mEnumerationIndex++, mRecycledSummaryEntry);
fillBucketFromSummaryEntry(bucketOut);
@@ -678,7 +680,7 @@
* @param bucketOut Next item will be set here.
* @return true if a next item could be set.
*/
- private boolean getNextHistoryBucket(Bucket bucketOut) {
+ private boolean getNextHistoryBucket(@Nullable Bucket bucketOut) {
if (bucketOut != null && mHistory != null) {
if (mEnumerationIndex < mHistory.size()) {
mRecycledHistoryEntry = mHistory.getValues(mEnumerationIndex++,
diff --git a/framework-t/src/android/app/usage/NetworkStatsManager.java b/framework-t/src/android/app/usage/NetworkStatsManager.java
index bf518b2..f41475b 100644
--- a/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -290,7 +290,7 @@
* statistics collection.
*/
@WorkerThread
- public Bucket querySummaryForDevice(int networkType, String subscriberId,
+ public Bucket querySummaryForDevice(int networkType, @Nullable String subscriberId,
long startTime, long endTime) throws SecurityException, RemoteException {
NetworkTemplate template;
try {
@@ -335,8 +335,8 @@
* statistics collection.
*/
@WorkerThread
- public Bucket querySummaryForUser(int networkType, String subscriberId, long startTime,
- long endTime) throws SecurityException, RemoteException {
+ public Bucket querySummaryForUser(int networkType, @Nullable String subscriberId,
+ long startTime, long endTime) throws SecurityException, RemoteException {
NetworkTemplate template;
try {
template = createTemplate(networkType, subscriberId);
@@ -384,7 +384,7 @@
* statistics collection.
*/
@WorkerThread
- public NetworkStats querySummary(int networkType, String subscriberId, long startTime,
+ public NetworkStats querySummary(int networkType, @Nullable String subscriberId, long startTime,
long endTime) throws SecurityException, RemoteException {
NetworkTemplate template;
try {
@@ -508,15 +508,17 @@
*
* @see #queryDetailsForUidTagState(int, String, long, long, int, int, int)
*/
+ @NonNull
@WorkerThread
- public NetworkStats queryDetailsForUid(int networkType, String subscriberId,
+ public NetworkStats queryDetailsForUid(int networkType, @Nullable String subscriberId,
long startTime, long endTime, int uid) throws SecurityException {
return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid,
NetworkStats.Bucket.TAG_NONE, NetworkStats.Bucket.STATE_ALL);
}
/** @hide */
- public NetworkStats queryDetailsForUid(NetworkTemplate template,
+ @NonNull
+ public NetworkStats queryDetailsForUid(@NonNull NetworkTemplate template,
long startTime, long endTime, int uid) throws SecurityException {
return queryDetailsForUidTagState(template, startTime, endTime, uid,
NetworkStats.Bucket.TAG_NONE, NetworkStats.Bucket.STATE_ALL);
@@ -524,23 +526,59 @@
/**
* Query network usage statistics details for a given uid and tag.
+ *
+ * This may take a long time, and apps should avoid calling this on their main thread.
+ * Only usable for uids belonging to calling user. Result is not aggregated over time.
+ * This means buckets' start and end timestamps are going to be between 'startTime' and
+ * 'endTime' parameters. The uid is going to be the same as the 'uid' parameter, the tag
+ * the same as the 'tag' parameter, and the state the same as the 'state' parameter.
+ * defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
+ * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and
+ * roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't
+ * interpolate across partial buckets. Since bucket length is in the order of hours, this
+ * method cannot be used to measure data usage on a fine grained time scale.
* This may take a long time, and apps should avoid calling this on their main thread.
*
- * @see #queryDetailsForUidTagState(int, String, long, long, int, int, int)
+ * @param networkType As defined in {@link ConnectivityManager}, e.g.
+ * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
+ * etc.
+ * @param subscriberId If applicable, the subscriber id of the network interface.
+ * <p>Starting with API level 29, the {@code subscriberId} is guarded by
+ * additional restrictions. Calling apps that do not meet the new
+ * requirements to access the {@code subscriberId} can provide a {@code
+ * null} value when querying for the mobile network type to receive usage
+ * for all mobile networks. For additional details see {@link
+ * TelephonyManager#getSubscriberId()}.
+ * <p>Starting with API level 31, calling apps can provide a
+ * {@code subscriberId} with wifi network type to receive usage for
+ * wifi networks which is under the given subscription if applicable.
+ * Otherwise, pass {@code null} when querying all wifi networks.
+ * @param startTime Start of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param endTime End of period. Defined in terms of "Unix time", see
+ * {@link java.lang.System#currentTimeMillis}.
+ * @param uid UID of app
+ * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for aggregated data
+ * across all the tags.
+ * @return Statistics which is described above.
+ * @throws SecurityException if permissions are insufficient to read network statistics.
*/
+ @NonNull
@WorkerThread
- public NetworkStats queryDetailsForUidTag(int networkType, String subscriberId,
+ public NetworkStats queryDetailsForUidTag(int networkType, @Nullable String subscriberId,
long startTime, long endTime, int uid, int tag) throws SecurityException {
return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid,
tag, NetworkStats.Bucket.STATE_ALL);
}
/**
- * Query network usage statistics details for a given uid, tag, and state. Only usable for uids
- * belonging to calling user. Result is not aggregated over time. This means buckets' start and
- * end timestamps are going to be between 'startTime' and 'endTime' parameters. The uid is going
- * to be the same as the 'uid' parameter, the tag the same as the 'tag' parameter, and the state
- * the same as the 'state' parameter.
+ * Query network usage statistics details for a given uid, tag, and state.
+ *
+ * Only usable for uids belonging to calling user. Result is not aggregated over time.
+ * This means buckets' start and end timestamps are going to be between 'startTime' and
+ * 'endTime' parameters. The uid is going to be the same as the 'uid' parameter, the tag
+ * the same as the 'tag' parameter, and the state the same as the 'state' parameter.
* defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
* metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and
* roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}.
@@ -572,11 +610,12 @@
* across all the tags.
* @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate
* traffic from all states.
- * @return Statistics object or null if an error happened during statistics collection.
+ * @return Statistics which is described above.
* @throws SecurityException if permissions are insufficient to read network statistics.
*/
+ @NonNull
@WorkerThread
- public NetworkStats queryDetailsForUidTagState(int networkType, String subscriberId,
+ public NetworkStats queryDetailsForUidTagState(int networkType, @Nullable String subscriberId,
long startTime, long endTime, int uid, int tag, int state) throws SecurityException {
NetworkTemplate template;
template = createTemplate(networkType, subscriberId);
@@ -669,7 +708,7 @@
* statistics collection.
*/
@WorkerThread
- public NetworkStats queryDetails(int networkType, String subscriberId, long startTime,
+ public NetworkStats queryDetails(int networkType, @Nullable String subscriberId, long startTime,
long endTime) throws SecurityException, RemoteException {
NetworkTemplate template;
try {
@@ -698,7 +737,7 @@
*
* @hide
*/
- @SystemApi
+ @SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(anyOf = {
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_STACK})
@@ -724,7 +763,7 @@
*
* @hide
*/
- @SystemApi
+ @SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(anyOf = {
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_STACK})
@@ -785,10 +824,28 @@
/**
* Registers to receive notifications about data usage on specified networks.
*
- * @see #registerUsageCallback(int, String, long, UsageCallback, Handler)
+ * <p>The callbacks will continue to be called as long as the process is live or
+ * {@link #unregisterUsageCallback} is called.
+ *
+ * @param networkType Type of network to monitor. Either
+ {@link ConnectivityManager#TYPE_MOBILE} or {@link ConnectivityManager#TYPE_WIFI}.
+ * @param subscriberId If applicable, the subscriber id of the network interface.
+ * <p>Starting with API level 29, the {@code subscriberId} is guarded by
+ * additional restrictions. Calling apps that do not meet the new
+ * requirements to access the {@code subscriberId} can provide a {@code
+ * null} value when registering for the mobile network type to receive
+ * notifications for all mobile networks. For additional details see {@link
+ * TelephonyManager#getSubscriberId()}.
+ * <p>Starting with API level 31, calling apps can provide a
+ * {@code subscriberId} with wifi network type to receive usage for
+ * wifi networks which is under the given subscription if applicable.
+ * Otherwise, pass {@code null} when querying all wifi networks.
+ * @param thresholdBytes Threshold in bytes to be notified on.
+ * @param callback The {@link UsageCallback} that the system will call when data usage
+ * has exceeded the specified threshold.
*/
- public void registerUsageCallback(int networkType, String subscriberId, long thresholdBytes,
- UsageCallback callback) {
+ public void registerUsageCallback(int networkType, @Nullable String subscriberId,
+ long thresholdBytes, @NonNull UsageCallback callback) {
registerUsageCallback(networkType, subscriberId, thresholdBytes, callback,
null /* handler */);
}
@@ -818,8 +875,8 @@
* @param handler to dispatch callback events through, otherwise if {@code null} it uses
* the calling thread.
*/
- public void registerUsageCallback(int networkType, String subscriberId, long thresholdBytes,
- UsageCallback callback, @Nullable Handler handler) {
+ public void registerUsageCallback(int networkType, @Nullable String subscriberId,
+ long thresholdBytes, @NonNull UsageCallback callback, @Nullable Handler handler) {
NetworkTemplate template = createTemplate(networkType, subscriberId);
if (DBG) {
Log.d(TAG, "registerUsageCallback called with: {"
@@ -839,7 +896,7 @@
*
* @param callback The {@link UsageCallback} used when registering.
*/
- public void unregisterUsageCallback(UsageCallback callback) {
+ public void unregisterUsageCallback(@NonNull UsageCallback callback) {
if (callback == null || callback.request == null
|| callback.request.requestId == DataUsageRequest.REQUEST_ID_UNSET) {
throw new IllegalArgumentException("Invalid UsageCallback");
@@ -880,7 +937,7 @@
/**
* Called when data usage has reached the given threshold.
*/
- public abstract void onThresholdReached(int networkType, String subscriberId);
+ public abstract void onThresholdReached(int networkType, @Nullable String subscriberId);
/**
* @hide used for internal bookkeeping
@@ -924,7 +981,7 @@
@RequiresPermission(anyOf = {
android.Manifest.permission.NETWORK_STATS_PROVIDER,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK})
- @NonNull public void registerNetworkStatsProvider(
+ public void registerNetworkStatsProvider(
@NonNull String tag,
@NonNull NetworkStatsProvider provider) {
try {
@@ -950,7 +1007,7 @@
@RequiresPermission(anyOf = {
android.Manifest.permission.NETWORK_STATS_PROVIDER,
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK})
- @NonNull public void unregisterNetworkStatsProvider(@NonNull NetworkStatsProvider provider) {
+ public void unregisterNetworkStatsProvider(@NonNull NetworkStatsProvider provider) {
try {
provider.getProviderCallbackBinderOrThrow().unregister();
} catch (RemoteException e) {
@@ -958,7 +1015,7 @@
}
}
- private static NetworkTemplate createTemplate(int networkType, String subscriberId) {
+ private static NetworkTemplate createTemplate(int networkType, @Nullable String subscriberId) {
final NetworkTemplate template;
switch (networkType) {
case ConnectivityManager.TYPE_MOBILE:
diff --git a/framework-t/src/android/net/NetworkIdentitySet.java b/framework-t/src/android/net/NetworkIdentitySet.java
index ad3a958..d88408e 100644
--- a/framework-t/src/android/net/NetworkIdentitySet.java
+++ b/framework-t/src/android/net/NetworkIdentitySet.java
@@ -206,6 +206,7 @@
public static int compare(@NonNull NetworkIdentitySet left, @NonNull NetworkIdentitySet right) {
Objects.requireNonNull(left);
Objects.requireNonNull(right);
+ if (left.isEmpty() && right.isEmpty()) return 0;
if (left.isEmpty()) return -1;
if (right.isEmpty()) return 1;
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
index 06f2a62..51ff5ec 100644
--- a/framework-t/src/android/net/NetworkStats.java
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -124,7 +124,6 @@
public @Nullable static final String[] INTERFACES_ALL = null;
/** {@link #tag} value for total data across all tags. */
- // TODO: Rename TAG_NONE to TAG_ALL.
public static final int TAG_NONE = 0;
/** {@link #metered} value to account for all metered states. */
@@ -412,21 +411,24 @@
/**
* @return the metered state.
*/
- @Meteredness public int getMetered() {
+ @Meteredness
+ public int getMetered() {
return metered;
}
/**
* @return the roaming state.
*/
- @Roaming public int getRoaming() {
+ @Roaming
+ public int getRoaming() {
return roaming;
}
/**
* @return the default network state.
*/
- @DefaultNetwork public int getDefaultNetwork() {
+ @DefaultNetwork
+ public int getDefaultNetwork() {
return defaultNetwork;
}
diff --git a/framework-t/src/android/net/NetworkStatsCollection.java b/framework-t/src/android/net/NetworkStatsCollection.java
index e385b33..b59a890 100644
--- a/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/framework-t/src/android/net/NetworkStatsCollection.java
@@ -776,7 +776,7 @@
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);
+ final Key groupKey = new Key(new NetworkIdentitySet(), key.uid, key.set, key.tag);
NetworkStatsHistory groupHistory = grouped.get(groupKey);
if (groupHistory == null) {
groupHistory = new NetworkStatsHistory(value.getBucketDuration());
diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java
index 7b5afd7..b82a126 100644
--- a/framework-t/src/android/net/NetworkTemplate.java
+++ b/framework-t/src/android/net/NetworkTemplate.java
@@ -393,8 +393,9 @@
//constructor passes METERED_YES for these types.
this(matchRule, subscriberId, matchSubscriberIds,
wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
- (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD) ? METERED_YES
- : METERED_ALL , ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD
+ || matchRule == MATCH_CARRIER) ? METERED_YES : METERED_ALL,
+ ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
OEM_MANAGED_ALL, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
}
diff --git a/framework-t/src/android/net/TrafficStats.java b/framework-t/src/android/net/TrafficStats.java
index bc836d8..dc4ac55 100644
--- a/framework-t/src/android/net/TrafficStats.java
+++ b/framework-t/src/android/net/TrafficStats.java
@@ -205,7 +205,7 @@
* server context.
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SystemApi(client = MODULE_LIBRARIES)
@SuppressLint("VisiblySynchronized")
public static synchronized void init(@NonNull final Context context) {
if (sStatsService != null) {
@@ -376,7 +376,7 @@
*
* @hide
*/
- @SystemApi
+ @SystemApi(client = MODULE_LIBRARIES)
public static void setThreadStatsTagDownload() {
setThreadStatsTag(TAG_SYSTEM_DOWNLOAD);
}
@@ -468,7 +468,7 @@
*
* @see #setThreadStatsTag(int)
*/
- public static void tagSocket(Socket socket) throws SocketException {
+ public static void tagSocket(@NonNull Socket socket) throws SocketException {
SocketTagger.get().tag(socket);
}
@@ -483,7 +483,7 @@
* calling {@code untagSocket()} before sending the socket to another
* process.
*/
- public static void untagSocket(Socket socket) throws SocketException {
+ public static void untagSocket(@NonNull Socket socket) throws SocketException {
SocketTagger.get().untag(socket);
}
@@ -496,14 +496,14 @@
*
* @see #setThreadStatsTag(int)
*/
- public static void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+ public static void tagDatagramSocket(@NonNull DatagramSocket socket) throws SocketException {
SocketTagger.get().tag(socket);
}
/**
* Remove any statistics parameters from the given {@link DatagramSocket}.
*/
- public static void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+ public static void untagDatagramSocket(@NonNull DatagramSocket socket) throws SocketException {
SocketTagger.get().untag(socket);
}
@@ -516,7 +516,7 @@
*
* @see #setThreadStatsTag(int)
*/
- public static void tagFileDescriptor(FileDescriptor fd) throws IOException {
+ public static void tagFileDescriptor(@NonNull FileDescriptor fd) throws IOException {
SocketTagger.get().tag(fd);
}
@@ -524,7 +524,7 @@
* Remove any statistics parameters from the given {@link FileDescriptor}
* socket.
*/
- public static void untagFileDescriptor(FileDescriptor fd) throws IOException {
+ public static void untagFileDescriptor(@NonNull FileDescriptor fd) throws IOException {
SocketTagger.get().untag(fd);
}
diff --git a/framework/Android.bp b/framework/Android.bp
index 3703df8..d7de439 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -92,6 +92,7 @@
"modules-utils-preconditions",
],
libs: [
+ "app-compat-annotations",
"framework-connectivity-t.stubs.module_lib",
"unsupportedappusage",
],
@@ -152,6 +153,11 @@
],
}
+platform_compat_config {
+ name: "connectivity-platform-compat-config",
+ src: ":framework-connectivity",
+}
+
cc_library_shared {
name: "libframework-connectivity-jni",
min_sdk_version: "30",
diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java
index 99f48b4..8782b33 100644
--- a/framework/src/android/net/LinkProperties.java
+++ b/framework/src/android/net/LinkProperties.java
@@ -19,12 +19,16 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.LinkPropertiesUtils;
import java.net.Inet4Address;
@@ -38,6 +42,7 @@
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
+import java.util.stream.Collectors;
/**
* Describes the properties of a network link.
@@ -52,6 +57,17 @@
*
*/
public final class LinkProperties implements Parcelable {
+ /**
+ * The {@link #getRoutes()} now can contain excluded as well as included routes. Use
+ * {@link RouteInfo#getType()} to determine route type.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S) // Switch to S_V2 when it is available.
+ @VisibleForTesting
+ public static final long EXCLUDED_ROUTES = 186082280;
+
// The interface described by the network link.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private String mIfaceName;
@@ -738,10 +754,25 @@
/**
* Returns all the {@link RouteInfo} set on this link.
*
+ * Only unicast routes are returned for apps targeting Android S or below.
+ *
* @return An unmodifiable {@link List} of {@link RouteInfo} for this link.
*/
public @NonNull List<RouteInfo> getRoutes() {
- return Collections.unmodifiableList(mRoutes);
+ if (CompatChanges.isChangeEnabled(EXCLUDED_ROUTES)) {
+ return Collections.unmodifiableList(mRoutes);
+ } else {
+ return Collections.unmodifiableList(getUnicastRoutes());
+ }
+ }
+
+ /**
+ * Returns all the {@link RouteInfo} of type {@link RouteInfo#RTN_UNICAST} set on this link.
+ */
+ private @NonNull List<RouteInfo> getUnicastRoutes() {
+ return mRoutes.stream()
+ .filter(route -> route.getType() == RouteInfo.RTN_UNICAST)
+ .collect(Collectors.toList());
}
/**
@@ -757,11 +788,14 @@
/**
* Returns all the routes on this link and all the links stacked above it.
+ *
+ * Only unicast routes are returned for apps targeting Android S or below.
+ *
* @hide
*/
@SystemApi
public @NonNull List<RouteInfo> getAllRoutes() {
- List<RouteInfo> routes = new ArrayList<>(mRoutes);
+ final List<RouteInfo> routes = new ArrayList<>(getRoutes());
for (LinkProperties stacked: mStackedLinks.values()) {
routes.addAll(stacked.getAllRoutes());
}
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index b28c006..0d2b620 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -24,6 +24,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.modules.utils.build.SdkLevel;
+
import java.util.Objects;
/**
@@ -473,6 +475,9 @@
@NonNull
@SystemApi(client = MODULE_LIBRARIES)
public Builder setLocalRoutesExcludedForVpn(boolean excludeLocalRoutes) {
+ if (!SdkLevel.isAtLeastT()) {
+ throw new UnsupportedOperationException("Method is not supported");
+ }
mConfig.excludeLocalRouteVpn = excludeLocalRoutes;
return this;
}
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index eb22f78..fe27335 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -19,7 +19,6 @@
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;
@@ -71,8 +70,6 @@
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<>();
@@ -99,25 +96,9 @@
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;
+ final ConnectivityResources resources = new ConnectivityResources(context);
+ return resources.get().getString(R.string.config_ethernet_tcp_buffers);
}
}
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 693d91a..e9053dd 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -25,7 +25,6 @@
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;
@@ -86,7 +85,6 @@
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,
@@ -134,48 +132,16 @@
}
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;
+ final ConnectivityResources resources = new ConnectivityResources(context);
+ return resources.get().getString(
+ com.android.connectivity.resources.R.string.config_ethernet_iface_regex);
}
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;
+ final ConnectivityResources resources = new ConnectivityResources(context);
+ return resources.get().getStringArray(
+ com.android.connectivity.resources.R.array.config_ethernet_interfaces);
}
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index d79bdb8..4835438 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -3266,11 +3266,12 @@
return;
}
- pw.print("NetworkProviders for:");
+ pw.println("NetworkProviders for:");
+ pw.increaseIndent();
for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
- pw.print(" " + npi.name);
+ pw.println(npi.providerId + ": " + npi.name);
}
- pw.println();
+ pw.decreaseIndent();
pw.println();
final NetworkAgentInfo defaultNai = getDefaultNetwork();
@@ -3319,6 +3320,14 @@
pw.decreaseIndent();
pw.println();
+ pw.println("Network Offers:");
+ pw.increaseIndent();
+ for (final NetworkOfferInfo offerInfo : mNetworkOffers) {
+ pw.println(offerInfo.offer);
+ }
+ pw.decreaseIndent();
+ pw.println();
+
mLegacyTypeTracker.dump(pw);
pw.println();
@@ -3669,7 +3678,7 @@
}
case NetworkAgent.EVENT_REMOVE_ALL_DSCP_POLICIES: {
if (mDscpPolicyTracker != null) {
- mDscpPolicyTracker.removeAllDscpPolicies(nai);
+ mDscpPolicyTracker.removeAllDscpPolicies(nai, true);
}
break;
}
@@ -4410,6 +4419,9 @@
}
private void destroyNativeNetwork(@NonNull NetworkAgentInfo nai) {
+ if (mDscpPolicyTracker != null) {
+ mDscpPolicyTracker.removeAllDscpPolicies(nai, false);
+ }
try {
mNetd.networkDestroy(nai.network.getNetId());
} catch (RemoteException | ServiceSpecificException e) {
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index a0bfb4a..ccc2776 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -231,6 +231,7 @@
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
nc.setNetworkSpecifier(new TestNetworkSpecifier(iface));
nc.setAdministratorUids(administratorUids);
if (!isMetered) {
diff --git a/service/src/com/android/server/connectivity/DscpPolicyTracker.java b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
index 53b276e..de9dfe3 100644
--- a/service/src/com/android/server/connectivity/DscpPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
@@ -19,6 +19,7 @@
import static android.net.NetworkAgent.DSCP_POLICY_STATUS_DELETED;
import static android.net.NetworkAgent.DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
import static android.net.NetworkAgent.DSCP_POLICY_STATUS_POLICY_NOT_FOUND;
+import static android.net.NetworkAgent.DSCP_POLICY_STATUS_REQUEST_DECLINED;
import static android.net.NetworkAgent.DSCP_POLICY_STATUS_SUCCESS;
import static android.system.OsConstants.ETH_P_ALL;
@@ -37,6 +38,7 @@
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.NetworkInterface;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
@@ -66,30 +68,68 @@
private final BpfMap<Struct.U32, DscpPolicyValue> mBpfDscpIpv4Policies;
private final BpfMap<Struct.U32, DscpPolicyValue> mBpfDscpIpv6Policies;
- private final SparseIntArray mPolicyIdToBpfMapIndex;
+
+ // The actual policy rules used by the BPF code to process packets
+ // are in mBpfDscpIpv4Policies and mBpfDscpIpv4Policies. Both of
+ // these can contain up to MAX_POLICIES rules.
+ //
+ // A given policy always consumes one entry in both the IPv4 and
+ // IPv6 maps even if if's an IPv4-only or IPv6-only policy.
+ //
+ // Each interface index has a SparseIntArray of rules which maps a
+ // policy ID to the index of the corresponding rule in the maps.
+ // mIfaceIndexToPolicyIdBpfMapIndex maps the interface index to
+ // the per-interface SparseIntArray.
+ private final HashMap<Integer, SparseIntArray> mIfaceIndexToPolicyIdBpfMapIndex;
public DscpPolicyTracker() throws ErrnoException {
mAttachedIfaces = new HashSet<String>();
-
- mPolicyIdToBpfMapIndex = new SparseIntArray(MAX_POLICIES);
+ mIfaceIndexToPolicyIdBpfMapIndex = new HashMap<Integer, SparseIntArray>();
mBpfDscpIpv4Policies = new BpfMap<Struct.U32, DscpPolicyValue>(IPV4_POLICY_MAP_PATH,
BpfMap.BPF_F_RDWR, Struct.U32.class, DscpPolicyValue.class);
mBpfDscpIpv6Policies = new BpfMap<Struct.U32, DscpPolicyValue>(IPV6_POLICY_MAP_PATH,
BpfMap.BPF_F_RDWR, Struct.U32.class, DscpPolicyValue.class);
}
+ private boolean isUnusedIndex(int index) {
+ for (SparseIntArray ifacePolicies : mIfaceIndexToPolicyIdBpfMapIndex.values()) {
+ if (ifacePolicies.indexOfValue(index) >= 0) return false;
+ }
+ return true;
+ }
+
private int getFirstFreeIndex() {
+ if (mIfaceIndexToPolicyIdBpfMapIndex.size() == 0) return 0;
for (int i = 0; i < MAX_POLICIES; i++) {
- if (mPolicyIdToBpfMapIndex.indexOfValue(i) < 0) return i;
+ if (isUnusedIndex(i)) {
+ return i;
+ }
}
return MAX_POLICIES;
}
+ private int findIndex(int policyId, int ifIndex) {
+ SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(ifIndex);
+ if (ifacePolicies != null) {
+ final int existingIndex = ifacePolicies.get(policyId, -1);
+ if (existingIndex != -1) {
+ return existingIndex;
+ }
+ }
+
+ final int firstIndex = getFirstFreeIndex();
+ if (firstIndex >= MAX_POLICIES) {
+ // New policy is being added, but max policies has already been reached.
+ return -1;
+ }
+ return firstIndex;
+ }
+
private void sendStatus(NetworkAgentInfo nai, int policyId, int status) {
try {
nai.networkAgent.onDscpPolicyStatusUpdated(policyId, status);
} catch (RemoteException e) {
- Log.d(TAG, "Failed update policy status: ", e);
+ Log.e(TAG, "Failed update policy status: ", e);
}
}
@@ -107,37 +147,43 @@
|| policy.getSourceAddress() instanceof Inet6Address));
}
- private int addDscpPolicyInternal(DscpPolicy policy) {
+ private int getIfaceIndex(NetworkAgentInfo nai) {
+ String iface = nai.linkProperties.getInterfaceName();
+ NetworkInterface netIface;
+ try {
+ netIface = NetworkInterface.getByName(iface);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to get iface index for " + iface + ": " + e);
+ netIface = null;
+ }
+ return (netIface != null) ? netIface.getIndex() : 0;
+ }
+
+ private int addDscpPolicyInternal(DscpPolicy policy, int ifIndex) {
// If there is no existing policy with a matching ID, and we are already at
// the maximum number of policies then return INSUFFICIENT_PROCESSING_RESOURCES.
- final int existingIndex = mPolicyIdToBpfMapIndex.get(policy.getPolicyId(), -1);
- if (existingIndex == -1 && mPolicyIdToBpfMapIndex.size() >= MAX_POLICIES) {
- return DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
+ SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(ifIndex);
+ if (ifacePolicies == null) {
+ ifacePolicies = new SparseIntArray(MAX_POLICIES);
}
// Currently all classifiers are supported, if any are removed return
// DSCP_POLICY_STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED,
// and for any other generic error DSCP_POLICY_STATUS_REQUEST_DECLINED
- int addIndex = 0;
- // If a policy with a matching ID exists, replace it, otherwise use the next free
- // index for the policy.
- if (existingIndex != -1) {
- addIndex = mPolicyIdToBpfMapIndex.get(policy.getPolicyId());
- } else {
- addIndex = getFirstFreeIndex();
+ final int addIndex = findIndex(policy.getPolicyId(), ifIndex);
+ if (addIndex == -1) {
+ return DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
}
try {
- mPolicyIdToBpfMapIndex.put(policy.getPolicyId(), addIndex);
-
// Add v4 policy to mBpfDscpIpv4Policies if source and destination address
- // are both null or if they are both instances of Inet6Address.
+ // are both null or if they are both instances of Inet4Address.
if (matchesIpv4(policy)) {
mBpfDscpIpv4Policies.insertOrReplaceEntry(
new Struct.U32(addIndex),
new DscpPolicyValue(policy.getSourceAddress(),
- policy.getDestinationAddress(),
+ policy.getDestinationAddress(), ifIndex,
policy.getSourcePort(), policy.getDestinationPortRange(),
(short) policy.getProtocol(), (short) policy.getDscpValue()));
}
@@ -148,10 +194,16 @@
mBpfDscpIpv6Policies.insertOrReplaceEntry(
new Struct.U32(addIndex),
new DscpPolicyValue(policy.getSourceAddress(),
- policy.getDestinationAddress(),
+ policy.getDestinationAddress(), ifIndex,
policy.getSourcePort(), policy.getDestinationPortRange(),
(short) policy.getProtocol(), (short) policy.getDscpValue()));
}
+
+ ifacePolicies.put(policy.getPolicyId(), addIndex);
+ // Only add the policy to the per interface map if the policy was successfully
+ // added to both bpf maps above. It is safe to assume that if insert fails for
+ // one map then it fails for both.
+ mIfaceIndexToPolicyIdBpfMapIndex.put(ifIndex, ifacePolicies);
} catch (ErrnoException e) {
Log.e(TAG, "Failed to insert policy into map: ", e);
return DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
@@ -166,6 +218,7 @@
*
* DSCP_POLICY_STATUS_SUCCESS - if policy was added successfully
* DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES - if max policies were already set
+ * DSCP_POLICY_STATUS_REQUEST_DECLINED - Interface index was invalid
*/
public void addDscpPolicy(NetworkAgentInfo nai, DscpPolicy policy) {
if (!mAttachedIfaces.contains(nai.linkProperties.getInterfaceName())) {
@@ -177,11 +230,19 @@
}
}
- int status = addDscpPolicyInternal(policy);
+ final int ifIndex = getIfaceIndex(nai);
+ if (ifIndex == 0) {
+ Log.e(TAG, "Iface index is invalid");
+ sendStatus(nai, policy.getPolicyId(), DSCP_POLICY_STATUS_REQUEST_DECLINED);
+ return;
+ }
+
+ int status = addDscpPolicyInternal(policy, ifIndex);
sendStatus(nai, policy.getPolicyId(), status);
}
- private void removePolicyFromMap(NetworkAgentInfo nai, int policyId, int index) {
+ private void removePolicyFromMap(NetworkAgentInfo nai, int policyId, int index,
+ boolean sendCallback) {
int status = DSCP_POLICY_STATUS_POLICY_NOT_FOUND;
try {
mBpfDscpIpv4Policies.replaceEntry(new Struct.U32(index), DscpPolicyValue.NONE);
@@ -191,7 +252,9 @@
Log.e(TAG, "Failed to delete policy from map: ", e);
}
- sendStatus(nai, policyId, status);
+ if (sendCallback) {
+ sendStatus(nai, policyId, status);
+ }
}
/**
@@ -204,36 +267,44 @@
return;
}
- if (mPolicyIdToBpfMapIndex.get(policyId, -1) != -1) {
- removePolicyFromMap(nai, policyId, mPolicyIdToBpfMapIndex.get(policyId));
- mPolicyIdToBpfMapIndex.delete(policyId);
+ SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(getIfaceIndex(nai));
+ if (ifacePolicies == null) return;
+
+ final int existingIndex = ifacePolicies.get(policyId, -1);
+ if (existingIndex == -1) {
+ Log.e(TAG, "Policy " + policyId + " does not exist in map.");
+ sendStatus(nai, policyId, DSCP_POLICY_STATUS_POLICY_NOT_FOUND);
+ return;
}
- // TODO: detach should only occur if no more policies are present on the nai's iface.
- if (mPolicyIdToBpfMapIndex.size() == 0) {
+ removePolicyFromMap(nai, policyId, existingIndex, true);
+ ifacePolicies.delete(policyId);
+
+ if (ifacePolicies.size() == 0) {
detachProgram(nai.linkProperties.getInterfaceName());
}
}
/**
- * Remove all DSCP policies and detach program.
+ * Remove all DSCP policies and detach program. Send callback if requested.
*/
- // TODO: Remove all should only remove policies from corresponding nai iface.
- public void removeAllDscpPolicies(NetworkAgentInfo nai) {
+ public void removeAllDscpPolicies(NetworkAgentInfo nai, boolean sendCallback) {
if (!mAttachedIfaces.contains(nai.linkProperties.getInterfaceName())) {
// Nothing to remove since program is not attached. Send update for policy
// id 0. The status update must contain a policy ID, and 0 is an invalid id.
- sendStatus(nai, 0, DSCP_POLICY_STATUS_SUCCESS);
+ if (sendCallback) {
+ sendStatus(nai, 0, DSCP_POLICY_STATUS_SUCCESS);
+ }
return;
}
- for (int i = 0; i < mPolicyIdToBpfMapIndex.size(); i++) {
- removePolicyFromMap(nai, mPolicyIdToBpfMapIndex.keyAt(i),
- mPolicyIdToBpfMapIndex.valueAt(i));
+ SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(getIfaceIndex(nai));
+ if (ifacePolicies == null) return;
+ for (int i = 0; i < ifacePolicies.size(); i++) {
+ removePolicyFromMap(nai, ifacePolicies.keyAt(i), ifacePolicies.valueAt(i),
+ sendCallback);
}
- mPolicyIdToBpfMapIndex.clear();
-
- // Can detach program since no policies are active.
+ ifacePolicies.clear();
detachProgram(nai.linkProperties.getInterfaceName());
}
@@ -241,12 +312,12 @@
* Attach BPF program
*/
private boolean attachProgram(@NonNull String iface) {
- // TODO: attach needs to be per iface not program.
-
try {
NetworkInterface netIface = NetworkInterface.getByName(iface);
+ boolean isEth = TcUtils.isEthernet(iface);
+ String path = PROG_PATH + (isEth ? "_ether" : "_raw_ip");
TcUtils.tcFilterAddDevBpf(netIface.getIndex(), false, PRIO_DSCP, (short) ETH_P_ALL,
- PROG_PATH);
+ path);
} catch (IOException e) {
Log.e(TAG, "Unable to attach to TC on " + iface + ": " + e);
return false;
@@ -264,9 +335,9 @@
if (netIface != null) {
TcUtils.tcFilterDelDev(netIface.getIndex(), false, PRIO_DSCP, (short) ETH_P_ALL);
}
+ mAttachedIfaces.remove(iface);
} catch (IOException e) {
Log.e(TAG, "Unable to detach to TC on " + iface + ": " + e);
}
- mAttachedIfaces.remove(iface);
}
}
diff --git a/service/src/com/android/server/connectivity/DscpPolicyValue.java b/service/src/com/android/server/connectivity/DscpPolicyValue.java
index cb40306..6e4e7eb 100644
--- a/service/src/com/android/server/connectivity/DscpPolicyValue.java
+++ b/service/src/com/android/server/connectivity/DscpPolicyValue.java
@@ -31,29 +31,31 @@
public class DscpPolicyValue extends Struct {
private static final String TAG = DscpPolicyValue.class.getSimpleName();
- // TODO: add the interface index.
@Field(order = 0, type = Type.ByteArray, arraysize = 16)
public final byte[] src46;
@Field(order = 1, type = Type.ByteArray, arraysize = 16)
public final byte[] dst46;
- @Field(order = 2, type = Type.UBE16)
- public final int srcPort;
+ @Field(order = 2, type = Type.U32)
+ public final long ifIndex;
@Field(order = 3, type = Type.UBE16)
- public final int dstPortStart;
+ public final int srcPort;
@Field(order = 4, type = Type.UBE16)
+ public final int dstPortStart;
+
+ @Field(order = 5, type = Type.UBE16)
public final int dstPortEnd;
- @Field(order = 5, type = Type.U8)
+ @Field(order = 6, type = Type.U8)
public final short proto;
- @Field(order = 6, type = Type.U8)
+ @Field(order = 7, type = Type.U8)
public final short dscp;
- @Field(order = 7, type = Type.U8, padding = 3)
+ @Field(order = 8, type = Type.U8, padding = 3)
public final short mask;
private static final int SRC_IP_MASK = 0x1;
@@ -69,6 +71,7 @@
return true;
}
+ // TODO: move to frameworks/libs/net and have this and BpfCoordinator import it.
private byte[] toIpv4MappedAddressBytes(InetAddress ia) {
final byte[] addr6 = new byte[16];
if (ia != null) {
@@ -117,13 +120,12 @@
return mask;
}
- // This constructor is necessary for BpfMap#getValue since all values must be
- // in the constructor.
- public DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final int srcPort,
- final int dstPortStart, final int dstPortEnd, final short proto,
+ private DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final long ifIndex,
+ final int srcPort, final int dstPortStart, final int dstPortEnd, final short proto,
final short dscp) {
this.src46 = toAddressField(src46);
this.dst46 = toAddressField(dst46);
+ this.ifIndex = ifIndex;
// These params need to be stored as 0 because uints are used in BpfMap.
// If they are -1 BpfMap write will throw errors.
@@ -138,15 +140,15 @@
this.mask = makeMask(this.src46, this.dst46, srcPort, dstPortStart, proto, dscp);
}
- public DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final int srcPort,
- final Range<Integer> dstPort, final short proto,
+ public DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final long ifIndex,
+ final int srcPort, final Range<Integer> dstPort, final short proto,
final short dscp) {
- this(src46, dst46, srcPort, dstPort != null ? dstPort.getLower() : -1,
+ this(src46, dst46, ifIndex, srcPort, dstPort != null ? dstPort.getLower() : -1,
dstPort != null ? dstPort.getUpper() : -1, proto, dscp);
}
public static final DscpPolicyValue NONE = new DscpPolicyValue(
- null /* src46 */, null /* dst46 */, -1 /* srcPort */,
+ null /* src46 */, null /* dst46 */, 0 /* ifIndex */, -1 /* srcPort */,
-1 /* dstPortStart */, -1 /* dstPortEnd */, (short) -1 /* proto */,
(short) 0 /* dscp */);
@@ -170,9 +172,9 @@
try {
return String.format(
- "src46: %s, dst46: %s, srcPort: %d, dstPortStart: %d, dstPortEnd: %d,"
- + " protocol: %d, dscp %s", srcIpString, dstIpString, srcPort, dstPortStart,
- dstPortEnd, proto, dscp);
+ "src46: %s, dst46: %s, ifIndex: %d, srcPort: %d, dstPortStart: %d,"
+ + " dstPortEnd: %d, protocol: %d, dscp %s", srcIpString, dstIpString,
+ ifIndex, srcPort, dstPortStart, dstPortEnd, proto, dscp);
} catch (IllegalArgumentException e) {
return String.format("String format error: " + e);
}
diff --git a/service/src/com/android/server/connectivity/NetworkOffer.java b/service/src/com/android/server/connectivity/NetworkOffer.java
index 1e975dd..eea382e 100644
--- a/service/src/com/android/server/connectivity/NetworkOffer.java
+++ b/service/src/com/android/server/connectivity/NetworkOffer.java
@@ -22,6 +22,7 @@
import android.net.NetworkRequest;
import android.os.RemoteException;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@@ -143,6 +144,11 @@
@Override
public String toString() {
- return "NetworkOffer [ Score " + score + " Caps " + caps + "]";
+ final ArrayList<Integer> neededRequestIds = new ArrayList<>();
+ for (final NetworkRequest request : mCurrentlyNeeded) {
+ neededRequestIds.add(request.requestId);
+ }
+ return "NetworkOffer [ Provider Id (" + providerId + ") " + score + " Caps "
+ + caps + " Needed by " + neededRequestIds + "]";
}
}
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 7bb7cb5..509e881 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -36,6 +36,7 @@
"modules-utils-build",
"net-tests-utils",
"net-utils-framework-common",
+ "platform-compat-test-rules",
"platform-test-annotations",
],
libs: [
diff --git a/tests/common/AndroidTest_Coverage.xml b/tests/common/AndroidTest_Coverage.xml
index 7c8e710..d4898b2 100644
--- a/tests/common/AndroidTest_Coverage.xml
+++ b/tests/common/AndroidTest_Coverage.xml
@@ -18,6 +18,8 @@
</target_preparer>
<option name="test-tag" value="ConnectivityCoverageTests" />
+ <!-- Tethering/Connectivity is a SDK 30+ module -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
<option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.connectivity.tests.coverage" />
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 4d85a57..8fc636a 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -20,6 +20,7 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
@@ -30,6 +31,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.compat.testing.PlatformCompatChangeRule;
import android.net.LinkProperties.ProvisioningChange;
import android.os.Build;
import android.system.OsConstants;
@@ -45,6 +47,9 @@
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -65,6 +70,9 @@
@Rule
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+ @Rule
+ public final PlatformCompatChangeRule compatChangeRule = new PlatformCompatChangeRule();
+
private static final InetAddress ADDRV4 = address("75.208.6.1");
private static final InetAddress ADDRV6 = address("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
private static final InetAddress DNS1 = address("75.208.7.1");
@@ -1254,6 +1262,7 @@
}
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
public void testRouteAddWithSameKey() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("wlan0");
@@ -1268,4 +1277,36 @@
lp.addRoute(new RouteInfo(v4, address("192.0.2.1"), "wlan0", RTN_THROW, 1460));
assertEquals(2, lp.getRoutes().size());
}
+
+ @Test @IgnoreUpTo(SC_V2)
+ @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+ public void testExcludedRoutesEnabled() {
+ final LinkProperties lp = new LinkProperties();
+ assertEquals(0, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV4, 0), RTN_UNREACHABLE));
+ assertEquals(1, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 0), RTN_THROW));
+ assertEquals(2, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(GATEWAY1));
+ assertEquals(3, lp.getRoutes().size());
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ @DisableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+ public void testExcludedRoutesDisabled() {
+ final LinkProperties lp = new LinkProperties();
+ assertEquals(0, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV4, 0), RTN_UNREACHABLE));
+ assertEquals(0, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 5), RTN_THROW));
+ assertEquals(0, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 2), RTN_UNICAST));
+ assertEquals(1, lp.getRoutes().size());
+ }
}
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 9ae5fab..c30e1d3 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -82,12 +82,11 @@
import android.util.ArraySet;
import android.util.Range;
-import androidx.test.runner.AndroidJUnit4;
-
import com.android.testutils.CompatUtil;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Rule;
import org.junit.Test;
@@ -99,8 +98,12 @@
import java.util.List;
import java.util.Set;
-@RunWith(AndroidJUnit4.class)
@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+// NetworkCapabilities is only updatable on S+, and this test covers behavior which implementation
+// is self-contained within NetworkCapabilities.java, so it does not need to be run on, or
+// compatible with, earlier releases.
+@IgnoreUpTo(Build.VERSION_CODES.R)
@ConnectivityModuleTest
public class NetworkCapabilitiesTest {
private static final String TEST_SSID = "TEST_SSID";
@@ -489,7 +492,7 @@
assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testOemPrivate() {
NetworkCapabilities nc = new NetworkCapabilities();
// By default OEM_PRIVATE is neither in the required or forbidden lists and the network is
@@ -516,7 +519,7 @@
assertFalse(nr.satisfiedByNetworkCapabilities(new NetworkCapabilities()));
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testForbiddenCapabilities() {
NetworkCapabilities network = new NetworkCapabilities();
@@ -630,7 +633,7 @@
return new Range<Integer>(from, to);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testSetAdministratorUids() {
NetworkCapabilities nc =
new NetworkCapabilities().setAdministratorUids(new int[] {2, 1, 3});
@@ -638,7 +641,7 @@
assertArrayEquals(new int[] {1, 2, 3}, nc.getAdministratorUids());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testSetAdministratorUidsWithDuplicates() {
try {
new NetworkCapabilities().setAdministratorUids(new int[] {1, 1});
@@ -750,7 +753,7 @@
() -> nc2.addTransportType(TRANSPORT_WIFI));
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R) // New behavior in updatable NetworkCapabilities (S+)
+ @Test
public void testSetNetworkSpecifierOnTestMultiTransportNc() {
final NetworkSpecifier specifier = CompatUtil.makeEthernetNetworkSpecifier("eth0");
NetworkCapabilities nc = new NetworkCapabilities.Builder()
@@ -859,7 +862,7 @@
assertEquals(TRANSPORT_TEST, transportTypes[3]);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testTelephonyNetworkSpecifier() {
final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
@@ -970,7 +973,7 @@
assertEquals(specifier, nc.getNetworkSpecifier());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testAdministratorUidsAndOwnerUid() {
// Test default owner uid.
// If the owner uid is not set, the default value should be Process.INVALID_UID.
@@ -1014,7 +1017,7 @@
return nc;
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testSubIds() throws Exception {
final NetworkCapabilities ncWithoutId = capsWithSubIds();
final NetworkCapabilities ncWithId = capsWithSubIds(TEST_SUBID1);
@@ -1036,7 +1039,7 @@
assertTrue(requestWithoutId.canBeSatisfiedBy(ncWithId));
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testEqualsSubIds() throws Exception {
assertEquals(capsWithSubIds(), capsWithSubIds());
assertNotEquals(capsWithSubIds(), capsWithSubIds(TEST_SUBID1));
@@ -1185,7 +1188,7 @@
}
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test
public void testBuilder() {
final int ownerUid = 1001;
final int signalStrength = -80;
@@ -1255,7 +1258,7 @@
}
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testBuilderWithoutDefaultCap() {
final NetworkCapabilities nc =
NetworkCapabilities.Builder.withoutDefaultCapabilities().build();
@@ -1266,12 +1269,12 @@
assertEquals(0, nc.getCapabilities().length);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testRestrictCapabilitiesForTestNetworkByNotOwnerWithNonRestrictedNc() {
testRestrictCapabilitiesForTestNetworkWithNonRestrictedNc(false /* isOwner */);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testRestrictCapabilitiesForTestNetworkByOwnerWithNonRestrictedNc() {
testRestrictCapabilitiesForTestNetworkWithNonRestrictedNc(true /* isOwner */);
}
@@ -1316,12 +1319,12 @@
assertEquals(expectedNcBuilder.build(), nonRestrictedNc);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testRestrictCapabilitiesForTestNetworkByNotOwnerWithRestrictedNc() {
testRestrictCapabilitiesForTestNetworkWithRestrictedNc(false /* isOwner */);
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
public void testRestrictCapabilitiesForTestNetworkByOwnerWithRestrictedNc() {
testRestrictCapabilitiesForTestNetworkWithRestrictedNc(true /* isOwner */);
}
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
index 28437c2..e7b2815 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
+++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
@@ -28,5 +28,5 @@
void sendNotification(int notificationId, String notificationType);
void registerNetworkCallback(in NetworkRequest request, in INetworkCallback cb);
void unregisterNetworkCallback();
- void scheduleJob(in JobInfo jobInfo);
+ int scheduleJob(in JobInfo jobInfo);
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index f460180..96ce65f 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -16,6 +16,7 @@
package com.android.cts.net.hostside;
+import static android.app.job.JobScheduler.RESULT_SUCCESS;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.os.BatteryManager.BATTERY_PLUGGED_AC;
import static android.os.BatteryManager.BATTERY_PLUGGED_USB;
@@ -151,7 +152,7 @@
protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 20_000; // 20 sec
- private static final long BROADCAST_TIMEOUT_MS = 15_000;
+ private static final long BROADCAST_TIMEOUT_MS = 5_000;
protected Context mContext;
protected Instrumentation mInstrumentation;
@@ -855,7 +856,8 @@
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setTransientExtras(extras)
.build();
- mServiceClient.scheduleJob(jobInfo);
+ assertEquals("Error scheduling " + jobInfo,
+ RESULT_SUCCESS, mServiceClient.scheduleJob(jobInfo));
forceRunJob(TEST_APP2_PKG, TEST_JOB_ID);
if (latch.await(JOB_NETWORK_STATE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
final int resultCode = result.get(0).first;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
index 8b70f9b..0610774 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
@@ -107,7 +107,7 @@
mService.unregisterNetworkCallback();
}
- public void scheduleJob(JobInfo jobInfo) throws RemoteException {
- mService.scheduleJob(jobInfo);
+ public int scheduleJob(JobInfo jobInfo) throws RemoteException {
+ return mService.scheduleJob(jobInfo);
}
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index b6218d2..56be3e3 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -25,7 +25,7 @@
import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_METERED;
import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE;
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
import static org.junit.Assert.assertEquals;
@@ -390,7 +390,7 @@
}
public static String executeShellCommand(String command) {
- final String result = runShellCommand(command).trim();
+ final String result = runShellCommandOrThrow(command).trim();
Log.d(TAG, "Output of '" + command + "': '" + result + "'");
return result;
}
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
index f2a7b3f..3ed5391 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -165,10 +165,10 @@
}
@Override
- public void scheduleJob(JobInfo jobInfo) {
+ public int scheduleJob(JobInfo jobInfo) {
final JobScheduler jobScheduler = getApplicationContext()
.getSystemService(JobScheduler.class);
- jobScheduler.schedule(jobInfo);
+ return jobScheduler.schedule(jobInfo);
}
};
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 9055861..a129108 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -151,6 +151,7 @@
import android.os.Looper;
import android.os.MessageQueue;
import android.os.Process;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -661,10 +662,12 @@
// CaptivePortalApiUrl & CaptivePortalData will be preserved if the given uid holds the
// NETWORK_SETTINGS permission.
- assertEquals(capportUrl,
+ assertNotNull(lp.getCaptivePortalApiUrl());
+ assertNotNull(lp.getCaptivePortalData());
+ assertEquals(lp.getCaptivePortalApiUrl(),
mCm.getRedactedLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg)
.getCaptivePortalApiUrl());
- assertEquals(capportData,
+ assertEquals(lp.getCaptivePortalData(),
mCm.getRedactedLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg)
.getCaptivePortalData());
});
@@ -887,9 +890,21 @@
//
// Note that this test this will still fail in instant mode if a device supports Ethernet
// via other hardware means. We are not currently aware of any such device.
- return (mContext.getSystemService(Context.ETHERNET_SERVICE) != null) ||
- mPackageManager.hasSystemFeature(FEATURE_ETHERNET) ||
- mPackageManager.hasSystemFeature(FEATURE_USB_HOST);
+ return hasEthernetService()
+ || mPackageManager.hasSystemFeature(FEATURE_ETHERNET)
+ || mPackageManager.hasSystemFeature(FEATURE_USB_HOST);
+ }
+
+ private boolean hasEthernetService() {
+ // On Q creating EthernetManager from a thread that does not have a looper (like the test
+ // thread) crashes because it tried to use Looper.myLooper() through the default Handler
+ // constructor to run onAvailabilityChanged callbacks. Use ServiceManager to check whether
+ // the service exists instead.
+ // TODO: remove once Q is no longer supported in MTS, as ServiceManager is hidden API
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
+ return ServiceManager.getService(Context.ETHERNET_SERVICE) != null;
+ }
+ return mContext.getSystemService(Context.ETHERNET_SERVICE) != null;
}
private boolean shouldBeSupported(int networkType) {
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index e8add6b..1e42fe6 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -48,9 +48,11 @@
import android.platform.test.annotations.AppModeFull
import android.system.Os
import android.system.OsConstants.AF_INET
+import android.system.OsConstants.AF_INET6
import android.system.OsConstants.IPPROTO_UDP
import android.system.OsConstants.SOCK_DGRAM
import android.system.OsConstants.SOCK_NONBLOCK
+import android.util.Log
import android.util.Range
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
@@ -71,6 +73,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import java.net.Inet4Address
+import java.net.Inet6Address
+import java.net.InetAddress
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.regex.Pattern
@@ -81,6 +85,9 @@
private const val MAX_PACKET_LENGTH = 1500
+private const val IP4_PREFIX_LEN = 32
+private const val IP6_PREFIX_LEN = 128
+
private val instrumentation: Instrumentation
get() = InstrumentationRegistry.getInstrumentation()
@@ -97,6 +104,9 @@
private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
private val TEST_TARGET_IPV4_ADDR =
InetAddresses.parseNumericAddress("8.8.8.8") as Inet4Address
+ private val LOCAL_IPV6_ADDRESS = InetAddresses.parseNumericAddress("2001:db8::1")
+ private val TEST_TARGET_IPV6_ADDR =
+ InetAddresses.parseNumericAddress("2001:4860:4860::8888") as Inet6Address
private val realContext = InstrumentationRegistry.getContext()
private val cm = realContext.getSystemService(ConnectivityManager::class.java)
@@ -132,7 +142,9 @@
runAsShell(MANAGE_TEST_NETWORKS) {
val tnm = realContext.getSystemService(TestNetworkManager::class.java)
- iface = tnm.createTunInterface(Array(1) { LinkAddress(LOCAL_IPV4_ADDRESS, 32) })
+ iface = tnm.createTunInterface(arrayOf(
+ LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN),
+ LinkAddress(LOCAL_IPV6_ADDRESS, IP6_PREFIX_LEN)))
assertNotNull(iface)
}
@@ -154,6 +166,8 @@
// reader.stop() cleans up tun fd
reader.handler.post { reader.stop() }
+ if (iface.fileDescriptor.fileDescriptor != null)
+ Os.close(iface.fileDescriptor.fileDescriptor)
handlerThread.quitSafely()
}
@@ -196,9 +210,11 @@
}
}
val lp = LinkProperties().apply {
- addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
+ addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN))
+ addLinkAddress(LinkAddress(LOCAL_IPV6_ADDRESS, IP6_PREFIX_LEN))
addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
- setInterfaceName(iface.getInterfaceName())
+ addRoute(RouteInfo(InetAddress.getByName("fe80::1234")))
+ setInterfaceName(specifier)
}
val config = NetworkAgentConfig.Builder().build()
val agent = TestableNetworkAgent(context, handlerThread.looper, nc, lp, config)
@@ -218,47 +234,114 @@
eachByte -> "%02x".format(eachByte)
}
- fun checkDscpValue(
+ fun sendPacket(
agent: TestableNetworkAgent,
- callback: TestableNetworkCallback,
- dscpValue: Int = 0,
- dstPort: Int = 0
+ sendV6: Boolean,
+ dstPort: Int = 0,
) {
val testString = "test string"
val testPacket = ByteBuffer.wrap(testString.toByteArray(Charsets.UTF_8))
var packetFound = false
- val socket = Os.socket(AF_INET, SOCK_DGRAM or SOCK_NONBLOCK, IPPROTO_UDP)
+ val socket = Os.socket(if (sendV6) AF_INET6 else AF_INET, SOCK_DGRAM or SOCK_NONBLOCK,
+ IPPROTO_UDP)
agent.network.bindSocket(socket)
val originalPacket = testPacket.readAsArray()
- Os.sendto(socket, originalPacket, 0 /* bytesOffset */, originalPacket.size,
- 0 /* flags */, TEST_TARGET_IPV4_ADDR, dstPort)
-
+ Os.sendto(socket, originalPacket, 0 /* bytesOffset */, originalPacket.size, 0 /* flags */,
+ if(sendV6) TEST_TARGET_IPV6_ADDR else TEST_TARGET_IPV4_ADDR, dstPort)
Os.close(socket)
+ }
+
+ fun parseV4PacketDscp(buffer : ByteBuffer) : Int {
+ val ip_ver = buffer.get()
+ val tos = buffer.get()
+ val length = buffer.getShort()
+ val id = buffer.getShort()
+ val offset = buffer.getShort()
+ val ttl = buffer.get()
+ val ipType = buffer.get()
+ val checksum = buffer.getShort()
+ return tos.toInt().shr(2)
+ }
+
+ fun parseV6PacketDscp(buffer : ByteBuffer) : Int {
+ val ip_ver = buffer.get()
+ val tc = buffer.get()
+ val fl = buffer.getShort()
+ val length = buffer.getShort()
+ val proto = buffer.get()
+ val hop = buffer.get()
+ // DSCP is bottom 4 bits of ip_ver and top 2 of tc.
+ val ip_ver_bottom = ip_ver.toInt().and(0xf)
+ val tc_dscp = tc.toInt().shr(6)
+ return ip_ver_bottom.toInt().shl(2) + tc_dscp
+ }
+
+ fun parsePacketIp(
+ buffer : ByteBuffer,
+ sendV6 : Boolean,
+ ) : Boolean {
+ val ipAddr = if (sendV6) ByteArray(16) else ByteArray(4)
+ buffer.get(ipAddr)
+ val srcIp = if (sendV6) Inet6Address.getByAddress(ipAddr)
+ else Inet4Address.getByAddress(ipAddr)
+ buffer.get(ipAddr)
+ val dstIp = if (sendV6) Inet6Address.getByAddress(ipAddr)
+ else Inet4Address.getByAddress(ipAddr)
+
+ Log.e(TAG, "IP Src:" + srcIp + " dst: " + dstIp)
+
+ if ((sendV6 && srcIp == LOCAL_IPV6_ADDRESS && dstIp == TEST_TARGET_IPV6_ADDR) ||
+ (!sendV6 && srcIp == LOCAL_IPV4_ADDRESS && dstIp == TEST_TARGET_IPV4_ADDR)) {
+ Log.e(TAG, "IP return true");
+ return true
+ }
+ Log.e(TAG, "IP return false");
+ return false
+ }
+
+ fun parsePacketPort(
+ buffer : ByteBuffer,
+ srcPort : Int,
+ dstPort : Int
+ ) : Boolean {
+ if (srcPort == 0 && dstPort == 0) return true
+
+ val packetSrcPort = buffer.getShort().toInt()
+ val packetDstPort = buffer.getShort().toInt()
+
+ Log.e(TAG, "Port Src:" + packetSrcPort + " dst: " + packetDstPort)
+
+ if ((srcPort == 0 || (srcPort != 0 && srcPort == packetSrcPort)) &&
+ (dstPort == 0 || (dstPort != 0 && dstPort == packetDstPort))) {
+ Log.e(TAG, "Port return true");
+ return true
+ }
+ Log.e(TAG, "Port return false");
+ return false
+ }
+
+ fun validatePacket(
+ agent : TestableNetworkAgent,
+ sendV6 : Boolean = false,
+ dscpValue : Int = 0,
+ dstPort : Int = 0,
+ ) {
+ var packetFound = false;
+ sendPacket(agent, sendV6, dstPort)
+ // TODO: grab source port from socket in sendPacket
+
+ Log.e(TAG, "find DSCP value:" + dscpValue)
generateSequence { reader.poll(PACKET_TIMEOUT_MS) }.forEach { packet ->
val buffer = ByteBuffer.wrap(packet, 0, packet.size).order(ByteOrder.BIG_ENDIAN)
- val ip_ver = buffer.get()
- val tos = buffer.get()
- val length = buffer.getShort()
- val id = buffer.getShort()
- val offset = buffer.getShort()
- val ttl = buffer.get()
- val ipType = buffer.get()
- val checksum = buffer.getShort()
+ val dscp = if (sendV6) parseV6PacketDscp(buffer) else parseV4PacketDscp(buffer)
+ Log.e(TAG, "DSCP value:" + dscp)
- val ipAddr = ByteArray(4)
- buffer.get(ipAddr)
- val srcIp = Inet4Address.getByAddress(ipAddr)
- buffer.get(ipAddr)
- val dstIp = Inet4Address.getByAddress(ipAddr)
- val packetSrcPort = buffer.getShort().toInt()
- val packetDstPort = buffer.getShort().toInt()
-
- // TODO: Add source port comparison.
- if (srcIp == LOCAL_IPV4_ADDRESS && dstIp == TEST_TARGET_IPV4_ADDR &&
- packetDstPort == dstPort) {
- assertEquals(dscpValue, (tos.toInt().shr(2)))
+ // TODO: Add source port comparison. Use 0 for now.
+ if (parsePacketIp(buffer, sendV6) && parsePacketPort(buffer, 0, dstPort)) {
+ Log.e(TAG, "DSCP value found")
+ assertEquals(dscpValue, dscp)
packetFound = true
}
}
@@ -275,12 +358,12 @@
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(policyId, it.policyId)
assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
- checkDscpValue(agent, callback, dstPort = portNumber)
}
}
@Test
- fun testDscpPolicyAddPolicies(): Unit = createConnectedNetworkAgent().let { (agent, callback) ->
+ fun testDscpPolicyAddPolicies(): Unit = createConnectedNetworkAgent().let {
+ (agent, callback) ->
val policy = DscpPolicy.Builder(1, 1)
.setDestinationPortRange(Range(4444, 4444)).build()
agent.sendAddDscpPolicy(policy)
@@ -288,8 +371,7 @@
assertEquals(1, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
}
-
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 4444)
+ validatePacket(agent, dscpValue = 1, dstPort = 4444)
agent.sendRemoveDscpPolicy(1)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
@@ -298,15 +380,54 @@
}
val policy2 = DscpPolicy.Builder(1, 4)
- .setDestinationPortRange(Range(5555, 5555)).setSourceAddress(LOCAL_IPV4_ADDRESS)
- .setDestinationAddress(TEST_TARGET_IPV4_ADDR).setProtocol(IPPROTO_UDP).build()
+ .setDestinationPortRange(Range(5555, 5555))
+ .setDestinationAddress(TEST_TARGET_IPV4_ADDR)
+ .setSourceAddress(LOCAL_IPV4_ADDRESS)
+ .setProtocol(IPPROTO_UDP).build()
agent.sendAddDscpPolicy(policy2)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
}
- checkDscpValue(agent, callback, dscpValue = 4, dstPort = 5555)
+ validatePacket(agent, dscpValue = 4, dstPort = 5555)
+
+ agent.sendRemoveDscpPolicy(1)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
+ }
+ }
+
+ @Test
+ fun testDscpPolicyAddV6Policies(): Unit = createConnectedNetworkAgent().let {
+ (agent, callback) ->
+ val policy = DscpPolicy.Builder(1, 1)
+ .setDestinationPortRange(Range(4444, 4444)).build()
+ agent.sendAddDscpPolicy(policy)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ }
+ validatePacket(agent, true, dscpValue = 1, dstPort = 4444)
+
+ agent.sendRemoveDscpPolicy(1)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
+ }
+
+ val policy2 = DscpPolicy.Builder(1, 4)
+ .setDestinationPortRange(Range(5555, 5555))
+ .setDestinationAddress(TEST_TARGET_IPV6_ADDR)
+ .setSourceAddress(LOCAL_IPV6_ADDRESS)
+ .setProtocol(IPPROTO_UDP).build()
+ agent.sendAddDscpPolicy(policy2)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
+ }
+ validatePacket(agent, true, dscpValue = 4, dstPort = 5555)
agent.sendRemoveDscpPolicy(1)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
@@ -324,7 +445,7 @@
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 1111)
+ validatePacket(agent, dscpValue = 1, dstPort = 1111)
}
val policy2 = DscpPolicy.Builder(2, 1).setDestinationPortRange(Range(2222, 2222)).build()
@@ -332,7 +453,7 @@
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(2, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 2222)
+ validatePacket(agent, dscpValue = 1, dstPort = 2222)
}
val policy3 = DscpPolicy.Builder(3, 1).setDestinationPortRange(Range(3333, 3333)).build()
@@ -340,13 +461,16 @@
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(3, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 3333)
+ validatePacket(agent, dscpValue = 1, dstPort = 3333)
}
/* Remove Policies and check CE is no longer set */
doRemovePolicyTest(agent, callback, 1)
+ validatePacket(agent, dscpValue = 0, dstPort = 1111)
doRemovePolicyTest(agent, callback, 2)
+ validatePacket(agent, dscpValue = 0, dstPort = 2222)
doRemovePolicyTest(agent, callback, 3)
+ validatePacket(agent, dscpValue = 0, dstPort = 3333)
}
@Test
@@ -357,7 +481,7 @@
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 1111)
+ validatePacket(agent, dscpValue = 1, dstPort = 1111)
}
doRemovePolicyTest(agent, callback, 1)
@@ -366,7 +490,7 @@
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(2, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 2222)
+ validatePacket(agent, dscpValue = 1, dstPort = 2222)
}
doRemovePolicyTest(agent, callback, 2)
@@ -375,7 +499,7 @@
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(3, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 3333)
+ validatePacket(agent, dscpValue = 1, dstPort = 3333)
}
doRemovePolicyTest(agent, callback, 3)
}
@@ -389,7 +513,7 @@
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 1111)
+ validatePacket(agent, dscpValue = 1, dstPort = 1111)
}
val policy2 = DscpPolicy.Builder(2, 1).setDestinationPortRange(Range(2222, 2222)).build()
@@ -397,7 +521,7 @@
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(2, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 2222)
+ validatePacket(agent, dscpValue = 1, dstPort = 2222)
}
val policy3 = DscpPolicy.Builder(3, 1).setDestinationPortRange(Range(3333, 3333)).build()
@@ -405,7 +529,7 @@
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(3, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 3333)
+ validatePacket(agent, dscpValue = 1, dstPort = 3333)
}
/* Remove Policies and check CE is no longer set */
@@ -423,14 +547,15 @@
}
@Test
- fun testRemoveAllDscpPolicies(): Unit = createConnectedNetworkAgent().let { (agent, callback) ->
+ fun testRemoveAllDscpPolicies(): Unit = createConnectedNetworkAgent().let {
+ (agent, callback) ->
val policy = DscpPolicy.Builder(1, 1)
.setDestinationPortRange(Range(1111, 1111)).build()
agent.sendAddDscpPolicy(policy)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 1111)
+ validatePacket(agent, dscpValue = 1, dstPort = 1111)
}
val policy2 = DscpPolicy.Builder(2, 1)
@@ -439,7 +564,7 @@
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(2, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 2222)
+ validatePacket(agent, dscpValue = 1, dstPort = 2222)
}
val policy3 = DscpPolicy.Builder(3, 1)
@@ -448,24 +573,24 @@
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(3, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 3333)
+ validatePacket(agent, dscpValue = 1, dstPort = 3333)
}
agent.sendRemoveAllDscpPolicies()
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
- checkDscpValue(agent, callback, dstPort = 1111)
+ validatePacket(agent, false, dstPort = 1111)
}
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(2, it.policyId)
assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
- checkDscpValue(agent, callback, dstPort = 2222)
+ validatePacket(agent, false, dstPort = 2222)
}
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(3, it.policyId)
assertEquals(DSCP_POLICY_STATUS_DELETED, it.status)
- checkDscpValue(agent, callback, dstPort = 3333)
+ validatePacket(agent, false, dstPort = 3333)
}
}
@@ -477,12 +602,9 @@
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
assertEquals(1, it.policyId)
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 4444)
+ validatePacket(agent, dscpValue = 1, dstPort = 4444)
}
- // TODO: send packet on socket and confirm that changing the DSCP policy
- // updates the mark to the new value.
-
val policy2 = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(5555, 5555)).build()
agent.sendAddDscpPolicy(policy2)
agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
@@ -490,8 +612,8 @@
assertEquals(DSCP_POLICY_STATUS_SUCCESS, it.status)
// Sending packet with old policy should fail
- checkDscpValue(agent, callback, dstPort = 4444)
- checkDscpValue(agent, callback, dscpValue = 1, dstPort = 5555)
+ validatePacket(agent, dscpValue = 0, dstPort = 4444)
+ validatePacket(agent, dscpValue = 1, dstPort = 5555)
}
agent.sendRemoveDscpPolicy(1)
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index 48cbd03..04843f9 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -30,6 +30,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -59,6 +60,7 @@
import androidx.test.InstrumentationRegistry;
import com.android.internal.util.HexDump;
+import com.android.networkstack.apishim.ConstantsShim;
import com.android.networkstack.apishim.Ikev2VpnProfileBuilderShimImpl;
import com.android.networkstack.apishim.Ikev2VpnProfileShimImpl;
import com.android.networkstack.apishim.VpnManagerShimImpl;
@@ -66,6 +68,7 @@
import com.android.networkstack.apishim.common.Ikev2VpnProfileShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.apishim.common.VpnManagerShim;
+import com.android.networkstack.apishim.common.VpnProfileStateShim;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -84,6 +87,7 @@
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@@ -193,6 +197,7 @@
private final X509Certificate mServerRootCa;
private final CertificateAndKey mUserCertKey;
+ private final List<TestableNetworkCallback> mCallbacksToUnregister = new ArrayList<>();
public Ikev2VpnTest() throws Exception {
// Build certificates
@@ -202,6 +207,9 @@
@After
public void tearDown() {
+ for (TestableNetworkCallback callback : mCallbacksToUnregister) {
+ sCM.unregisterNetworkCallback(callback);
+ }
setAppop(AppOpsManager.OP_ACTIVATE_VPN, false);
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
}
@@ -481,12 +489,19 @@
final TestableNetworkCallback cb = new TestableNetworkCallback(TIMEOUT_MS);
final NetworkRequest nr = new NetworkRequest.Builder()
.clearCapabilities().addTransportType(TRANSPORT_VPN).build();
- sCM.registerNetworkCallback(nr, cb);
+ registerNetworkCallback(nr, cb);
if (testSessionKey) {
// testSessionKey will never be true if running on <T
// startProvisionedVpnProfileSession() should return a non-null & non-empty random UUID.
- assertFalse(TextUtils.isEmpty(mVmShim.startProvisionedVpnProfileSession()));
+ final String sessionId = mVmShim.startProvisionedVpnProfileSession();
+ assertFalse(TextUtils.isEmpty(sessionId));
+ final VpnProfileStateShim profileState = mVmShim.getProvisionedVpnProfileState();
+ assertNotNull(profileState);
+ assertEquals(ConstantsShim.VPN_PROFILE_STATE_CONNECTING, profileState.getState());
+ assertEquals(sessionId, profileState.getSessionId());
+ assertFalse(profileState.isAlwaysOn());
+ assertFalse(profileState.isLockdownEnabled());
} else {
sVpnMgr.startProvisionedVpnProfile();
}
@@ -502,7 +517,16 @@
final Network vpnNetwork = cb.expectCallback(CallbackEntry.AVAILABLE, anyNetwork())
.getNetwork();
- cb.expectCapabilitiesThat(vpnNetwork, TIMEOUT_MS, caps -> caps.hasTransport(TRANSPORT_VPN)
+ if (testSessionKey) {
+ final VpnProfileStateShim profileState = mVmShim.getProvisionedVpnProfileState();
+ assertNotNull(profileState);
+ assertEquals(ConstantsShim.VPN_PROFILE_STATE_CONNECTED, profileState.getState());
+ assertFalse(profileState.isAlwaysOn());
+ assertFalse(profileState.isLockdownEnabled());
+ }
+
+ cb.expectCapabilitiesThat(vpnNetwork, TIMEOUT_MS,
+ caps -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasCapability(NET_CAPABILITY_INTERNET)
&& !caps.hasCapability(NET_CAPABILITY_VALIDATED)
&& Process.myUid() == caps.getOwnerUid());
@@ -515,8 +539,10 @@
// but unexpectedly sends this callback, expecting LOST below will fail because the next
// callback will be the validated capabilities instead.
// In S and below, |requiresValidation| is ignored, so this callback is always sent
- // regardless of its value.
- if (!requiresValidation || !TestUtils.shouldTestTApis()) {
+ // regardless of its value. However, there is a race in Vpn(see b/228574221) that VPN may
+ // misuse VPN network itself as the underlying network. The fix is not available without
+ // SDK > T platform. Thus, verify this only on T+ platform.
+ if (!requiresValidation && TestUtils.shouldTestTApis()) {
cb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED, TIMEOUT_MS,
entry -> ((CallbackEntry.CapabilitiesChanged) entry).getCaps()
.hasCapability(NET_CAPABILITY_VALIDATED));
@@ -529,6 +555,11 @@
lost -> vpnNetwork.equals(lost.getNetwork()));
}
+ private void registerNetworkCallback(NetworkRequest request, TestableNetworkCallback callback) {
+ sCM.registerNetworkCallback(request, callback);
+ mCallbacksToUnregister.add(callback);
+ }
+
private class VerifyStartStopVpnProfileTest implements TestNetworkRunnable.Test {
private final boolean mTestIpv6Only;
private final boolean mRequiresValidation;
diff --git a/tests/native/Android.bp b/tests/native/Android.bp
index 9c286d8..a8d908a 100644
--- a/tests/native/Android.bp
+++ b/tests/native/Android.bp
@@ -9,8 +9,8 @@
"mts-tethering",
"vts",
],
+ test_config_template: "AndroidTestTemplate.xml",
min_sdk_version: "31",
- require_root: true,
tidy: false,
srcs: [
"connectivity_native_test.cpp",
@@ -30,5 +30,4 @@
"libutils",
],
compile_multilib: "first",
- defaults: ["connectivity-mainline-presubmit-cc-defaults"],
}
diff --git a/tests/native/AndroidTestTemplate.xml b/tests/native/AndroidTestTemplate.xml
new file mode 100644
index 0000000..44e35a9
--- /dev/null
+++ b/tests/native/AndroidTestTemplate.xml
@@ -0,0 +1,30 @@
+<!-- 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.
+-->
+<configuration description="Configuration for connectivity {MODULE} tests">
+ <option name="test-suite-tag" value="mts" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
+ <!-- The tested code is only part of a SDK 30+ module (Tethering) -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="{MODULE}" />
+ </test>
+</configuration>
diff --git a/tests/native/connectivity_native_test.cpp b/tests/native/connectivity_native_test.cpp
index 8b089ab..3db5265 100644
--- a/tests/native/connectivity_native_test.cpp
+++ b/tests/native/connectivity_native_test.cpp
@@ -14,14 +14,15 @@
* limitations under the License.
*/
+#include <aidl/android/net/connectivity/aidl/ConnectivityNative.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
#include <android-modules-utils/sdk_level.h>
#include <cutils/misc.h> // FIRST_APPLICATION_UID
#include <gtest/gtest.h>
#include <netinet/in.h>
-#include <android/binder_manager.h>
-#include <android/binder_process.h>
-#include <aidl/android/net/connectivity/aidl/ConnectivityNative.h>
+#include "bpf/BpfUtils.h"
using aidl::android::net::connectivity::aidl::IConnectivityNative;
@@ -40,6 +41,10 @@
if (!android::modules::sdklevel::IsAtLeastT()) GTEST_SKIP() <<
"Should be at least T device.";
+ // Skip test case if not on 5.4 kernel which is required by bpf prog.
+ if (!android::bpf::isAtLeastKernelVersion(5, 4, 0)) GTEST_SKIP() <<
+ "Kernel should be at least 5.4.";
+
ASSERT_NE(nullptr, mService.get());
// If there are already ports being blocked on device unblockAllPortsForBind() store
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 545f7b9..5b926de 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -63,6 +63,7 @@
"java/android/net/IpSecManagerTest.java",
"java/android/net/IpSecTransformTest.java",
"java/android/net/KeepalivePacketDataUtilTest.java",
+ "java/android/net/NetworkIdentitySetTest.kt",
"java/android/net/NetworkIdentityTest.kt",
"java/android/net/NetworkStats*.java",
"java/android/net/NetworkTemplateTest.kt",
diff --git a/tests/unit/java/android/net/NetworkIdentitySetTest.kt b/tests/unit/java/android/net/NetworkIdentitySetTest.kt
new file mode 100644
index 0000000..d61ebf9
--- /dev/null
+++ b/tests/unit/java/android/net/NetworkIdentitySetTest.kt
@@ -0,0 +1,66 @@
+/*
+ * 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
+
+import android.content.Context
+import android.net.ConnectivityManager.TYPE_MOBILE
+import android.os.Build
+import android.telephony.TelephonyManager
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import kotlin.test.assertEquals
+
+private const val TEST_IMSI1 = "testimsi1"
+
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+@RunWith(DevSdkIgnoreRunner::class)
+class NetworkIdentitySetTest {
+ private val mockContext = mock(Context::class.java)
+
+ private fun buildMobileNetworkStateSnapshot(
+ caps: NetworkCapabilities,
+ subscriberId: String
+ ): NetworkStateSnapshot {
+ return NetworkStateSnapshot(mock(Network::class.java), caps,
+ LinkProperties(), subscriberId, TYPE_MOBILE)
+ }
+
+ @Test
+ fun testCompare() {
+ val ident1 = NetworkIdentity.buildNetworkIdentity(mockContext,
+ buildMobileNetworkStateSnapshot(NetworkCapabilities(), TEST_IMSI1),
+ false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
+ val ident2 = NetworkIdentity.buildNetworkIdentity(mockContext,
+ buildMobileNetworkStateSnapshot(NetworkCapabilities(), TEST_IMSI1),
+ true /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
+
+ // Verify that the results of comparing two empty sets are equal
+ assertEquals(0, NetworkIdentitySet.compare(NetworkIdentitySet(), NetworkIdentitySet()))
+
+ val identSet1 = NetworkIdentitySet()
+ val identSet2 = NetworkIdentitySet()
+ identSet1.add(ident1)
+ identSet2.add(ident2)
+ assertEquals(-1, NetworkIdentitySet.compare(NetworkIdentitySet(), identSet1))
+ assertEquals(1, NetworkIdentitySet.compare(identSet1, NetworkIdentitySet()))
+ assertEquals(0, NetworkIdentitySet.compare(identSet1, identSet1))
+ assertEquals(-1, NetworkIdentitySet.compare(identSet1, identSet2))
+ }
+}