Merge "DscpPolicy BPF IPv4 Checksum Offset, DSCP Value Storage"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 28edc8a..bfba5cd 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -37,6 +37,7 @@
],
static_libs: [
"androidx.annotation_annotation",
+ "connectivity-net-module-utils-bpf",
"modules-utils-build",
"modules-utils-statemachine",
"networkstack-client",
diff --git a/Tethering/apex/manifest.json b/Tethering/apex/manifest.json
index 3cb03ed..5d5ede6 100644
--- a/Tethering/apex/manifest.json
+++ b/Tethering/apex/manifest.json
@@ -1,4 +1,7 @@
{
"name": "com.android.tethering",
- "version": 339990000
+
+ // Placeholder module version to be replaced during build.
+ // Do not change!
+ "version": 0
}
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 b865a8e..18ef631 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
@@ -19,7 +19,6 @@
import android.net.INetd;
import android.net.MacAddress;
import android.net.TetherStatsParcel;
-import android.net.util.SharedLog;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.SparseArray;
@@ -28,6 +27,7 @@
import androidx.annotation.Nullable;
import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
+import com.android.net.module.util.SharedLog;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.bpf.TetherStatsValue;
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 0683e5e..fd9dab5 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
@@ -19,7 +19,6 @@
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
import android.net.MacAddress;
-import android.net.util.SharedLog;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -31,6 +30,7 @@
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
+import com.android.net.module.util.SharedLog;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.bpf.TetherStatsKey;
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 437ed71..da7ca56 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -46,7 +46,6 @@
import android.net.dhcp.IDhcpServer;
import android.net.ip.IpNeighborMonitor.NeighborEvent;
import android.net.ip.RouterAdvertisementDaemon.RaParams;
-import android.net.util.SharedLog;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -64,6 +63,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetdUtils;
+import com.android.net.module.util.SharedLog;
import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.BpfCoordinator.ClientInfo;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 1368eee..133ae01 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -44,14 +44,12 @@
import android.net.ip.ConntrackMonitor.ConntrackEventConsumer;
import android.net.ip.IpServer;
import android.net.netstats.provider.NetworkStatsProvider;
-import android.net.util.SharedLog;
import android.os.Handler;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.text.TextUtils;
import android.util.ArraySet;
-import android.util.Base64;
import android.util.Log;
import android.util.SparseArray;
@@ -61,10 +59,12 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.BpfDump;
import com.android.net.module.util.BpfMap;
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.SharedLog;
import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.U32;
import com.android.net.module.util.bpf.Tether4Key;
@@ -125,9 +125,6 @@
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 = ",";
-
/** The names of all the BPF counters defined in bpf_tethering.h. */
public static final String[] sBpfCounterNames = getBpfCounterNames();
@@ -1080,18 +1077,6 @@
}
}
- 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", "");
- final byte[] valueBytes = value.writeToBytes();
- final String valueBase64Str = Base64.encodeToString(valueBytes, Base64.DEFAULT)
- .replace("\n", "");
-
- return keyBase64Str + DUMP_BASE64_DELIMITER + valueBase64Str;
- }
-
private <K extends Struct, V extends Struct> void dumpRawMap(BpfMap<K, V> map,
IndentingPrintWriter pw) throws ErrnoException {
if (map == null) {
@@ -1102,14 +1087,20 @@
pw.println("No entries");
return;
}
- map.forEach((k, v) -> pw.println(bpfMapEntryToBase64String(k, v)));
+ map.forEach((k, v) -> pw.println(BpfDump.toBase64EncodedString(k, v)));
}
/**
- * Dump raw BPF map in base64 encoded strings. For test only.
- * Only allow to dump one map path once.
- * Format:
+ * Dump raw BPF map into the base64 encoded strings "<base64 key>,<base64 value>".
+ * Allow to dump only one map path once. For test only.
+ *
+ * Usage:
* $ dumpsys tethering bpfRawMap --<map name>
+ *
+ * Output:
+ * <base64 encoded key #1>,<base64 encoded value #1>
+ * <base64 encoded key #2>,<base64 encoded value #2>
+ * ..
*/
public void dumpRawMap(@NonNull IndentingPrintWriter pw, @Nullable String[] args) {
// TODO: consider checking the arg order that <map name> is after "bpfRawMap". Probably
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index adc95ab..741af5c 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -43,7 +43,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.net.util.SharedLog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
@@ -55,6 +54,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.SharedLog;
import java.io.PrintWriter;
import java.util.BitSet;
diff --git a/Tethering/src/com/android/networkstack/tethering/IPv6TetheringCoordinator.java b/Tethering/src/com/android/networkstack/tethering/IPv6TetheringCoordinator.java
index f3dcaa2..ab3929d 100644
--- a/Tethering/src/com/android/networkstack/tethering/IPv6TetheringCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/IPv6TetheringCoordinator.java
@@ -24,9 +24,10 @@
import android.net.RouteInfo;
import android.net.ip.IpServer;
import android.net.util.NetworkConstants;
-import android.net.util.SharedLog;
import android.util.Log;
+import com.android.net.module.util.SharedLog;
+
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/Tethering/src/com/android/networkstack/tethering/OffloadController.java
index d60c21d..94684af 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadController.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadController.java
@@ -43,7 +43,6 @@
import android.net.NetworkStats.Entry;
import android.net.RouteInfo;
import android.net.netstats.provider.NetworkStatsProvider;
-import android.net.util.SharedLog;
import android.os.Handler;
import android.provider.Settings;
import android.system.ErrnoException;
@@ -53,6 +52,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.SharedLog;
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/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 9da66d8..fbb342d 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -28,7 +28,6 @@
import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
-import android.net.util.SharedLog;
import android.net.util.SocketUtils;
import android.os.Handler;
import android.os.NativeHandle;
@@ -40,6 +39,7 @@
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.SharedLog;
import com.android.net.module.util.netlink.NetlinkSocket;
import com.android.net.module.util.netlink.StructNfGenMsg;
import com.android.net.module.util.netlink.StructNlMsgHdr;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index f613b73..47b1bd7 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -99,7 +99,6 @@
import android.net.TetheringRequestParcel;
import android.net.ip.IpServer;
import android.net.shared.NetdUtils;
-import android.net.util.SharedLog;
import android.net.wifi.WifiClient;
import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pGroup;
@@ -136,6 +135,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.common.BluetoothPanShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 7c36054..696a970 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -30,7 +30,6 @@
import android.content.Context;
import android.content.res.Resources;
import android.net.TetheringConfigurationParcel;
-import android.net.util.SharedLog;
import android.os.PersistableBundle;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -42,6 +41,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.SharedLog;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 8e0354d..611d1cf 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -22,7 +22,6 @@
import android.content.Context;
import android.net.INetd;
import android.net.ip.IpServer;
-import android.net.util.SharedLog;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -32,6 +31,7 @@
import androidx.annotation.NonNull;
import com.android.internal.util.StateMachine;
+import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.BluetoothPanShimImpl;
import com.android.networkstack.apishim.common.BluetoothPanShim;
import com.android.networkstack.tethering.metrics.TetheringMetrics;
diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
index f8dd673..16c031b 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
@@ -36,7 +36,6 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
-import android.net.util.SharedLog;
import android.os.Handler;
import android.util.Log;
import android.util.SparseIntArray;
@@ -46,6 +45,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StateMachine;
+import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.ConnectivityManagerShimImpl;
import com.android.networkstack.apishim.common.ConnectivityManagerShim;
import com.android.networkstack.tethering.util.PrefixUtils;
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 31c3df3..ca8d3de 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -32,6 +32,7 @@
"net-tests-utils",
"net-utils-device-common-bpf",
"testables",
+ "connectivity-net-module-utils-bpf",
],
libs: [
"android.test.runner",
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 7ccb7f5..ef4f052 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -32,6 +32,7 @@
import static android.system.OsConstants.IPPROTO_IPV6;
import static android.system.OsConstants.IPPROTO_UDP;
+import static com.android.net.module.util.BpfDump.BASE64_DELIMITER;
import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
import static com.android.net.module.util.HexDump.dumpHexString;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
@@ -156,7 +157,6 @@
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";
// version=6, traffic class=0x0, flowlabel=0x0;
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
index 7ee69b2..d38a7c3 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
@@ -28,7 +28,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import android.net.util.SharedLog;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -38,6 +37,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.SharedLog;
import com.android.net.module.util.netlink.StructNlMsgHdr;
import org.junit.Before;
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index bf7e887..ef143de 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -86,7 +86,6 @@
import android.net.ip.IpNeighborMonitor.NeighborEvent;
import android.net.ip.IpNeighborMonitor.NeighborEventConsumer;
import android.net.ip.RouterAdvertisementDaemon.RaParams;
-import android.net.util.SharedLog;
import android.os.Build;
import android.os.Handler;
import android.os.RemoteException;
@@ -101,6 +100,7 @@
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.SharedLog;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.bpf.TetherStatsKey;
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 3630f24..20a222d 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -85,7 +85,6 @@
import android.net.ip.ConntrackMonitor;
import android.net.ip.ConntrackMonitor.ConntrackEventConsumer;
import android.net.ip.IpServer;
-import android.net.util.SharedLog;
import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
@@ -100,6 +99,7 @@
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.SharedLog;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.bpf.TetherStatsKey;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index 01d7b4b..e4263db 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -65,7 +65,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.net.util.SharedLog;
import android.os.Bundle;
import android.os.Handler;
import android.os.PersistableBundle;
@@ -82,6 +81,7 @@
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.SharedLog;
import com.android.testutils.DevSdkIgnoreRule;
import org.junit.After;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
index ac5c59d..95ec38f 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
@@ -18,7 +18,8 @@
import android.content.Context;
import android.content.res.Resources;
-import android.net.util.SharedLog;
+
+import com.android.net.module.util.SharedLog;
/** FakeTetheringConfiguration is used to override static method for testing. */
public class FakeTetheringConfiguration extends TetheringConfiguration {
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/IPv6TetheringCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/IPv6TetheringCoordinatorTest.java
index f2b5314..865228a 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/IPv6TetheringCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/IPv6TetheringCoordinatorTest.java
@@ -41,11 +41,12 @@
import android.net.NetworkCapabilities;
import android.net.RouteInfo;
import android.net.ip.IpServer;
-import android.net.util.SharedLog;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.SharedLog;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
index 8ef0c76..faca1c8 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -67,7 +67,6 @@
import android.net.NetworkStats.Entry;
import android.net.RouteInfo;
import android.net.netstats.provider.NetworkStatsProvider;
-import android.net.util.SharedLog;
import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
@@ -79,6 +78,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.net.module.util.SharedLog;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.TestableNetworkStatsProviderCbBinder;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
index d1891ed..36b439b 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
@@ -43,7 +43,6 @@
import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
import android.hardware.tetheroffload.control.V1_1.OffloadCallbackEvent;
-import android.net.util.SharedLog;
import android.os.Handler;
import android.os.NativeHandle;
import android.os.test.TestLooper;
@@ -55,6 +54,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.SharedLog;
import com.android.net.module.util.netlink.StructNfGenMsg;
import com.android.net.module.util.netlink.StructNlMsgHdr;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index 3190f35..1a12125 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -47,7 +47,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.net.util.SharedLog;
import android.os.Build;
import android.os.PersistableBundle;
import android.provider.DeviceConfig;
@@ -63,6 +62,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.SharedLog;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
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 773cae3..cc80174 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -156,7 +156,6 @@
import android.net.ip.IpServer;
import android.net.ip.RouterAdvertisementDaemon;
import android.net.util.NetworkConstants;
-import android.net.util.SharedLog;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiClient;
import android.net.wifi.WifiManager;
@@ -189,6 +188,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.common.BluetoothPanShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
index 97cebd8..9b9507b 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
@@ -49,7 +49,6 @@
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
-import android.net.util.SharedLog;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -60,6 +59,7 @@
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import com.android.net.module.util.SharedLog;
import com.android.networkstack.tethering.TestConnectivityManager.NetworkRequestInfo;
import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
diff --git a/common/Android.bp b/common/Android.bp
new file mode 100644
index 0000000..729ef32
--- /dev/null
+++ b/common/Android.bp
@@ -0,0 +1,45 @@
+//
+// 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 {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+ name: "connectivity-net-module-utils-bpf",
+ srcs: [
+ "src/com/android/net/module/util/bpf/*.java",
+ ],
+ sdk_version: "module_current",
+ min_sdk_version: "29",
+ visibility: [
+ // Do not add any lib. This library is only shared inside connectivity module
+ // and its tests.
+ "//packages/modules/Connectivity:__subpackages__",
+ ],
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-connectivity.stubs.module_lib",
+ ],
+ static_libs: [
+ "net-utils-device-common-struct",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
+ lint: { strict_updatability_linting: true },
+}
diff --git a/common/src/com/android/net/module/util/bpf/ClatEgress4Key.java b/common/src/com/android/net/module/util/bpf/ClatEgress4Key.java
new file mode 100644
index 0000000..12200ec
--- /dev/null
+++ b/common/src/com/android/net/module/util/bpf/ClatEgress4Key.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.bpf;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet4Address;
+
+/** Key type for clat egress IPv4 maps. */
+public class ClatEgress4Key extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long iif; // The input interface index
+
+ @Field(order = 1, type = Type.Ipv4Address)
+ public final Inet4Address local4; // The source IPv4 address
+
+ public ClatEgress4Key(final long iif, final Inet4Address local4) {
+ this.iif = iif;
+ this.local4 = local4;
+ }
+}
diff --git a/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java b/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java
new file mode 100644
index 0000000..c10cb4d
--- /dev/null
+++ b/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.bpf;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+
+/** Value type for clat egress IPv4 maps. */
+public class ClatEgress4Value extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long oif; // The output interface to redirect to
+
+ @Field(order = 1, type = Type.Ipv6Address)
+ public final Inet6Address local6; // The full 128-bits of the source IPv6 address
+
+ @Field(order = 2, type = Type.Ipv6Address)
+ public final Inet6Address pfx96; // The destination /96 nat64 prefix, bottom 32 bits must be 0
+
+ @Field(order = 3, type = Type.U8, padding = 3)
+ public final short oifIsEthernet; // Whether the output interface requires ethernet header
+
+ public ClatEgress4Value(final long oif, final Inet6Address local6, final Inet6Address pfx96,
+ final short oifIsEthernet) {
+ this.oif = oif;
+ this.local6 = local6;
+ this.pfx96 = pfx96;
+ this.oifIsEthernet = oifIsEthernet;
+ }
+}
diff --git a/common/src/com/android/net/module/util/bpf/ClatIngress6Key.java b/common/src/com/android/net/module/util/bpf/ClatIngress6Key.java
new file mode 100644
index 0000000..1e2f4e0
--- /dev/null
+++ b/common/src/com/android/net/module/util/bpf/ClatIngress6Key.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.bpf;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+
+/** Key type for clat ingress IPv6 maps. */
+public class ClatIngress6Key extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long iif; // The input interface index
+
+ @Field(order = 1, type = Type.Ipv6Address)
+ public final Inet6Address pfx96; // The source /96 nat64 prefix, bottom 32 bits must be 0
+
+ @Field(order = 2, type = Type.Ipv6Address)
+ public final Inet6Address local6; // The full 128-bits of the destination IPv6 address
+
+ public ClatIngress6Key(final long iif, final Inet6Address pfx96, final Inet6Address local6) {
+ this.iif = iif;
+ this.pfx96 = pfx96;
+ this.local6 = local6;
+ }
+}
diff --git a/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java b/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java
new file mode 100644
index 0000000..bfec44f
--- /dev/null
+++ b/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.bpf;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet4Address;
+
+/** Value type for clat ingress IPv6 maps. */
+public class ClatIngress6Value extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long oif; // The output interface to redirect to (0 means don't redirect)
+
+ @Field(order = 1, type = Type.Ipv4Address)
+ public final Inet4Address local4; // The destination IPv4 address
+
+ public ClatIngress6Value(final long oif, final Inet4Address local4) {
+ this.oif = oif;
+ this.local4 = local4;
+ }
+}
diff --git a/common/src/com/android/net/module/util/bpf/Tether4Key.java b/common/src/com/android/net/module/util/bpf/Tether4Key.java
new file mode 100644
index 0000000..638576f
--- /dev/null
+++ b/common/src/com/android/net/module/util/bpf/Tether4Key.java
@@ -0,0 +1,81 @@
+/*
+ * 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.net.module.util.bpf;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet4Address;
+import java.net.UnknownHostException;
+import java.util.Objects;
+
+/** Key type for downstream & upstream IPv4 forwarding maps. */
+public class Tether4Key extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long iif;
+
+ @Field(order = 1, type = Type.EUI48)
+ public final MacAddress dstMac;
+
+ @Field(order = 2, type = Type.U8, padding = 1)
+ public final short l4proto;
+
+ @Field(order = 3, type = Type.ByteArray, arraysize = 4)
+ public final byte[] src4;
+
+ @Field(order = 4, type = Type.ByteArray, arraysize = 4)
+ public final byte[] dst4;
+
+ @Field(order = 5, type = Type.UBE16)
+ public final int srcPort;
+
+ @Field(order = 6, type = Type.UBE16)
+ public final int dstPort;
+
+ public Tether4Key(final long iif, @NonNull final MacAddress dstMac, final short l4proto,
+ final byte[] src4, final byte[] dst4, final int srcPort,
+ final int dstPort) {
+ Objects.requireNonNull(dstMac);
+
+ this.iif = iif;
+ this.dstMac = dstMac;
+ this.l4proto = l4proto;
+ this.src4 = src4;
+ this.dst4 = dst4;
+ this.srcPort = srcPort;
+ this.dstPort = dstPort;
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return String.format(
+ "iif: %d, dstMac: %s, l4proto: %d, src4: %s, dst4: %s, "
+ + "srcPort: %d, dstPort: %d",
+ iif, dstMac, l4proto,
+ Inet4Address.getByAddress(src4), Inet4Address.getByAddress(dst4),
+ Short.toUnsignedInt((short) srcPort), Short.toUnsignedInt((short) dstPort));
+ } catch (UnknownHostException | IllegalArgumentException e) {
+ return String.format("Invalid IP address", e);
+ }
+ }
+}
diff --git a/common/src/com/android/net/module/util/bpf/Tether4Value.java b/common/src/com/android/net/module/util/bpf/Tether4Value.java
new file mode 100644
index 0000000..de98766
--- /dev/null
+++ b/common/src/com/android/net/module/util/bpf/Tether4Value.java
@@ -0,0 +1,97 @@
+/*
+ * 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.net.module.util.bpf;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Objects;
+
+/** Value type for downstream & upstream IPv4 forwarding maps. */
+public class Tether4Value extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long oif;
+
+ // The ethhdr struct which is defined in uapi/linux/if_ether.h
+ @Field(order = 1, type = Type.EUI48)
+ public final MacAddress ethDstMac;
+ @Field(order = 2, type = Type.EUI48)
+ public final MacAddress ethSrcMac;
+ @Field(order = 3, type = Type.UBE16)
+ public final int ethProto; // Packet type ID field.
+
+ @Field(order = 4, type = Type.U16)
+ public final int pmtu;
+
+ @Field(order = 5, type = Type.ByteArray, arraysize = 16)
+ public final byte[] src46;
+
+ @Field(order = 6, type = Type.ByteArray, arraysize = 16)
+ public final byte[] dst46;
+
+ @Field(order = 7, type = Type.UBE16)
+ public final int srcPort;
+
+ @Field(order = 8, type = Type.UBE16)
+ public final int dstPort;
+
+ // TODO: consider using U64.
+ @Field(order = 9, type = Type.U63)
+ public final long lastUsed;
+
+ public Tether4Value(final long oif, @NonNull final MacAddress ethDstMac,
+ @NonNull final MacAddress ethSrcMac, final int ethProto, final int pmtu,
+ final byte[] src46, final byte[] dst46, final int srcPort,
+ final int dstPort, final long lastUsed) {
+ Objects.requireNonNull(ethDstMac);
+ Objects.requireNonNull(ethSrcMac);
+
+ this.oif = oif;
+ this.ethDstMac = ethDstMac;
+ this.ethSrcMac = ethSrcMac;
+ this.ethProto = ethProto;
+ this.pmtu = pmtu;
+ this.src46 = src46;
+ this.dst46 = dst46;
+ this.srcPort = srcPort;
+ this.dstPort = dstPort;
+ this.lastUsed = lastUsed;
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return String.format(
+ "oif: %d, ethDstMac: %s, ethSrcMac: %s, ethProto: %d, pmtu: %d, "
+ + "src46: %s, dst46: %s, srcPort: %d, dstPort: %d, "
+ + "lastUsed: %d",
+ oif, ethDstMac, ethSrcMac, ethProto, pmtu,
+ InetAddress.getByAddress(src46), InetAddress.getByAddress(dst46),
+ Short.toUnsignedInt((short) srcPort), Short.toUnsignedInt((short) dstPort),
+ lastUsed);
+ } catch (UnknownHostException | IllegalArgumentException e) {
+ return String.format("Invalid IP address", e);
+ }
+ }
+}
diff --git a/common/src/com/android/net/module/util/bpf/TetherStatsKey.java b/common/src/com/android/net/module/util/bpf/TetherStatsKey.java
new file mode 100644
index 0000000..c6d595b
--- /dev/null
+++ b/common/src/com/android/net/module/util/bpf/TetherStatsKey.java
@@ -0,0 +1,53 @@
+/*
+ * 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.net.module.util.bpf;
+
+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/common/src/com/android/net/module/util/bpf/TetherStatsValue.java b/common/src/com/android/net/module/util/bpf/TetherStatsValue.java
new file mode 100644
index 0000000..028d217
--- /dev/null
+++ b/common/src/com/android/net/module/util/bpf/TetherStatsValue.java
@@ -0,0 +1,80 @@
+/*
+ * 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.net.module.util.bpf;
+
+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/framework/Android.bp b/framework/Android.bp
index 24d8cca..6350e14 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -92,6 +92,7 @@
"framework-connectivity-javastream-protos",
],
libs: [
+ "androidx.annotation_annotation",
"app-compat-annotations",
"framework-connectivity-t.stubs.module_lib",
"unsupportedappusage",
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 97b1f32..dbb05a9 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -3043,7 +3043,7 @@
* <p>
* This list cannot be null, but it can be empty to mean that no UID without the
* {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission
- * gets to access this network.
+ * can access this network.
*
* @param uids the list of UIDs that can always access this network
* @return this builder
diff --git a/framework/src/android/net/NetworkProvider.java b/framework/src/android/net/NetworkProvider.java
index 0665af5..3615075 100644
--- a/framework/src/android/net/NetworkProvider.java
+++ b/framework/src/android/net/NetworkProvider.java
@@ -192,21 +192,36 @@
private class NetworkOfferCallbackProxy extends INetworkOfferCallback.Stub {
@NonNull public final NetworkOfferCallback callback;
@NonNull private final Executor mExecutor;
+ /**
+ * Boolean flag that prevents onNetworkNeeded / onNetworkUnneeded callbacks from being
+ * propagated after unregisterNetworkOffer has been called. Since unregisterNetworkOffer
+ * runs on the CS handler thread, it will not go into effect immediately.
+ */
+ private volatile boolean mIsStale;
NetworkOfferCallbackProxy(@NonNull final NetworkOfferCallback callback,
@NonNull final Executor executor) {
this.callback = callback;
this.mExecutor = executor;
+ this.mIsStale = false;
}
@Override
public void onNetworkNeeded(final @NonNull NetworkRequest request) {
- mExecutor.execute(() -> callback.onNetworkNeeded(request));
+ mExecutor.execute(() -> {
+ if (!mIsStale) callback.onNetworkNeeded(request);
+ });
}
@Override
public void onNetworkUnneeded(final @NonNull NetworkRequest request) {
- mExecutor.execute(() -> callback.onNetworkUnneeded(request));
+ mExecutor.execute(() -> {
+ if (!mIsStale) callback.onNetworkUnneeded(request);
+ });
+ }
+
+ public void markStale() {
+ mIsStale = true;
}
}
@@ -326,7 +341,10 @@
public void unregisterNetworkOffer(final @NonNull NetworkOfferCallback callback) {
final NetworkOfferCallbackProxy proxy = findProxyForCallback(callback);
if (null == proxy) return;
- mProxies.remove(proxy);
+ synchronized (mProxies) {
+ proxy.markStale();
+ mProxies.remove(proxy);
+ }
mContext.getSystemService(ConnectivityManager.class).unofferNetwork(proxy);
}
}
diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java
index 9cae9e6..788834a 100644
--- a/framework/src/android/net/TestNetworkManager.java
+++ b/framework/src/android/net/TestNetworkManager.java
@@ -195,6 +195,24 @@
/**
* Create a tap interface for testing purposes
*
+ * @param linkAddrs an array of LinkAddresses to assign to the TAP interface
+ * @return A TestNetworkInterface representing the underlying TAP interface. Close the contained
+ * ParcelFileDescriptor to tear down the TAP interface.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS)
+ @NonNull
+ public TestNetworkInterface createTapInterface(@NonNull LinkAddress[] linkAddrs) {
+ try {
+ return mService.createInterface(TAP, CARRIER_UP, BRING_UP, linkAddrs, null /* iface */);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Create a tap interface for testing purposes
+ *
* @param bringUp whether to bring up the interface before returning it.
*
* @return A ParcelFileDescriptor of the underlying TAP interface. Close this to tear down the
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index c67821f..6605428 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -58,13 +58,7 @@
}
int bpfGetUidStats(uid_t uid, Stats* stats) {
- BpfMapRO<uint32_t, StatsValue> appUidStatsMap(APP_UID_STATS_MAP_PATH);
-
- if (!appUidStatsMap.isValid()) {
- int ret = -errno;
- ALOGE("Opening appUidStatsMap(%s) failed: %s", APP_UID_STATS_MAP_PATH, strerror(errno));
- return ret;
- }
+ static BpfMapRO<uint32_t, StatsValue> appUidStatsMap(APP_UID_STATS_MAP_PATH);
return bpfGetUidStatsInternal(uid, stats, appUidStatsMap);
}
@@ -100,19 +94,8 @@
}
int bpfGetIfaceStats(const char* iface, Stats* stats) {
- BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
- int ret;
- if (!ifaceStatsMap.isValid()) {
- ret = -errno;
- ALOGE("get ifaceStats map fd failed: %s", strerror(errno));
- return ret;
- }
- BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
- if (!ifaceIndexNameMap.isValid()) {
- ret = -errno;
- ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
- return ret;
- }
+ static BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
+ static BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
return bpfGetIfaceStatsInternal(iface, stats, ifaceStatsMap, ifaceIndexNameMap);
}
@@ -186,19 +169,8 @@
int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines,
const std::vector<std::string>& limitIfaces, int limitTag,
int limitUid) {
- BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
- if (!ifaceIndexNameMap.isValid()) {
- int ret = -errno;
- ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
- return ret;
- }
-
- BpfMapRO<uint32_t, uint32_t> configurationMap(CONFIGURATION_MAP_PATH);
- if (!configurationMap.isValid()) {
- int ret = -errno;
- ALOGE("get configuration map fd failed: %s", strerror(errno));
- return ret;
- }
+ static BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
+ static BpfMapRO<uint32_t, uint32_t> configurationMap(CONFIGURATION_MAP_PATH);
auto configuration = configurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY);
if (!configuration.ok()) {
ALOGE("Cannot read the old configuration from map: %s",
@@ -210,12 +182,8 @@
return -EINVAL;
}
const char* statsMapPath = STATS_MAP_PATH[configuration.value()];
+ // TODO: fix this to not constantly reopen the bpf map
BpfMap<StatsKey, StatsValue> statsMap(statsMapPath);
- if (!statsMap.isValid()) {
- int ret = -errno;
- ALOGE("get stats map fd failed: %s, path: %s", strerror(errno), statsMapPath);
- return ret;
- }
// It is safe to read and clear the old map now since the
// networkStatsFactory should call netd to swap the map in advance already.
@@ -266,20 +234,8 @@
}
int parseBpfNetworkStatsDev(std::vector<stats_line>* lines) {
- int ret = 0;
- BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
- if (!ifaceIndexNameMap.isValid()) {
- ret = -errno;
- ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
- return ret;
- }
-
- BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
- if (!ifaceStatsMap.isValid()) {
- ret = -errno;
- ALOGE("get ifaceStats map fd failed: %s", strerror(errno));
- return ret;
- }
+ static BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
+ static BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
return parseBpfNetworkStatsDevInternal(lines, ifaceStatsMap, ifaceIndexNameMap);
}
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index c4ea9ae..cedffe1 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -60,7 +60,9 @@
import java.util.concurrent.ConcurrentHashMap;
/**
- * {@link NetworkProvider} that manages NetworkOffers for Ethernet networks.
+ * Class that manages NetworkOffers for Ethernet networks.
+ *
+ * TODO: this class should be merged into EthernetTracker.
*/
public class EthernetNetworkFactory {
private final static String TAG = EthernetNetworkFactory.class.getSimpleName();
@@ -221,11 +223,17 @@
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- protected void removeInterface(String interfaceName) {
+ protected boolean removeInterface(String interfaceName) {
NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
if (iface != null) {
- iface.destroy();
+ iface.unregisterNetworkOfferAndStop();
+ return true;
}
+ // TODO(b/236892130): if an interface is currently in server mode, it may not be properly
+ // removed.
+ // TODO: when false is returned, do not send a STATE_ABSENT callback.
+ Log.w(TAG, interfaceName + " is not tracked and cannot be removed");
+ return false;
}
/** Returns true if state has been modified */
@@ -292,7 +300,7 @@
private boolean mLinkUp;
private int mLegacyType;
private LinkProperties mLinkProperties = new LinkProperties();
- private Set<NetworkRequest> mRequests = new ArraySet<>();
+ private final Set<Integer> mRequestIds = new ArraySet<>();
private volatile @Nullable IpClientManager mIpClient;
private @NonNull NetworkCapabilities mCapabilities;
@@ -401,7 +409,7 @@
// existing requests.
// ConnectivityService filters requests for us based on the NetworkCapabilities
// passed in the registerNetworkOffer() call.
- mRequests.add(request);
+ mRequestIds.add(request.requestId);
// if the network is already started, this is a no-op.
start();
}
@@ -412,8 +420,12 @@
Log.d(TAG,
String.format("%s: onNetworkUnneeded for request: %s", name, request));
}
- mRequests.remove(request);
- if (mRequests.isEmpty()) {
+ if (!mRequestIds.remove(request.requestId)) {
+ // This can only happen if onNetworkNeeded was not called for a request or if
+ // the requestId changed. Both should *never* happen.
+ Log.wtf(TAG, "onNetworkUnneeded called for unknown request");
+ }
+ if (mRequestIds.isEmpty()) {
// not currently serving any requests, stop the network.
stop();
}
@@ -454,7 +466,7 @@
+ "transport type.");
}
- private static NetworkScore getBestNetworkScore() {
+ private static NetworkScore getNetworkScore() {
return new NetworkScore.Builder().build();
}
@@ -465,9 +477,7 @@
if (mLinkUp) {
// registering a new network offer will update the existing one, not install a
// new one.
- mNetworkProvider.registerNetworkOffer(getBestNetworkScore(),
- new NetworkCapabilities(capabilities), cmd -> mHandler.post(cmd),
- mNetworkOfferCallback);
+ registerNetworkOffer();
}
}
@@ -629,14 +639,12 @@
if (!up) { // was up, goes down
// retract network offer and stop IpClient.
- destroy();
+ unregisterNetworkOfferAndStop();
// If only setting the interface down, send a callback to signal completion.
EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, name, null);
} else { // was down, goes up
// register network offer
- mNetworkProvider.registerNetworkOffer(getBestNetworkScore(),
- new NetworkCapabilities(mCapabilities), (cmd) -> mHandler.post(cmd),
- mNetworkOfferCallback);
+ registerNetworkOffer();
}
return true;
@@ -660,10 +668,16 @@
mLinkProperties.clear();
}
- public void destroy() {
+ private void registerNetworkOffer() {
+ mNetworkProvider.registerNetworkOffer(getNetworkScore(),
+ new NetworkCapabilities(mCapabilities), cmd -> mHandler.post(cmd),
+ mNetworkOfferCallback);
+ }
+
+ public void unregisterNetworkOfferAndStop() {
mNetworkProvider.unregisterNetworkOffer(mNetworkOfferCallback);
stop();
- mRequests.clear();
+ mRequestIds.clear();
}
private static void provisionIpClient(@NonNull final IpClientManager ipClient,
diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java
index 3b93f1a..b628251 100644
--- a/service-t/src/com/android/server/net/NetworkStatsFactory.java
+++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java
@@ -164,16 +164,17 @@
}
public NetworkStatsFactory(@NonNull Context ctx) {
- this(ctx, new File("/proc/"), true);
+ this(ctx, new File("/proc/"), true, new BpfNetMaps());
}
@VisibleForTesting
- public NetworkStatsFactory(@NonNull Context ctx, File procRoot, boolean useBpfStats) {
+ public NetworkStatsFactory(@NonNull Context ctx, File procRoot, boolean useBpfStats,
+ BpfNetMaps bpfNetMaps) {
mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
mUseBpfStats = useBpfStats;
- mBpfNetMaps = new BpfNetMaps();
+ mBpfNetMaps = bpfNetMaps;
synchronized (mPersistentDataLock) {
mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
diff --git a/service-t/src/com/android/server/net/NetworkStatsRecorder.java b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
index 7c801d7..3da1585 100644
--- a/service-t/src/com/android/server/net/NetworkStatsRecorder.java
+++ b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
@@ -541,7 +541,8 @@
/**
* Recover from {@link FileRotator} failure by dumping state to
- * {@link DropBoxManager} and deleting contents.
+ * {@link DropBoxManager} and deleting contents if this recorder
+ * sets {@code mWipeOnError} to true, otherwise keep the contents.
*/
void recoverAndDeleteData() {
if (DUMP_BEFORE_DELETE) {
diff --git a/service/Android.bp b/service/Android.bp
index c2dbce1..7a4fb33 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -158,6 +158,7 @@
static_libs: [
// Do not add libs here if they are already included
// in framework-connectivity
+ "connectivity-net-module-utils-bpf",
"connectivity_native_aidl_interface-lateststable-java",
"dnsresolver_aidl_interface-V9-java",
"modules-utils-shell-command-handler",
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index 4013d2e..1ad75e3 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -92,7 +92,6 @@
rule android.net.util.KeepalivePacketDataUtil* com.android.connectivity.@0
# From connectivity-module-utils
-rule android.net.util.SharedLog* com.android.connectivity.@0
rule android.net.shared.** com.android.connectivity.@0
# From services-connectivity-shared-srcs
diff --git a/service/mdns/com/android/server/connectivity/mdns/util/MdnsLogger.java b/service/mdns/com/android/server/connectivity/mdns/util/MdnsLogger.java
index 31c62f5..431f1fd 100644
--- a/service/mdns/com/android/server/connectivity/mdns/util/MdnsLogger.java
+++ b/service/mdns/com/android/server/connectivity/mdns/util/MdnsLogger.java
@@ -16,9 +16,10 @@
package com.android.server.connectivity.mdns.util;
-import android.net.util.SharedLog;
import android.text.TextUtils;
+import com.android.net.module.util.SharedLog;
+
/**
* The logger used in mDNS.
*/
@@ -58,4 +59,4 @@
public void w(String message) {
mLog.w(message);
}
-}
\ No newline at end of file
+}
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 81e9e3a..be0eb5f 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -36,6 +36,7 @@
import android.util.Log;
import android.util.SparseLongArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BpfMap;
@@ -50,10 +51,16 @@
* {@hide}
*/
public class BpfNetMaps {
+ private static final boolean PRE_T = !SdkLevel.isAtLeastT();
+ static {
+ if (!PRE_T) {
+ System.loadLibrary("service-connectivity");
+ }
+ }
+
private static final String TAG = "BpfNetMaps";
private final INetd mNetd;
// Use legacy netd for releases before T.
- private static final boolean PRE_T = !SdkLevel.isAtLeastT();
private static boolean sInitialized = false;
// Lock for sConfigurationMap entry for UID_RULES_CONFIGURATION_KEY.
@@ -63,23 +70,27 @@
private static final String CONFIGURATION_MAP_PATH =
"/sys/fs/bpf/netd_shared/map_netd_configuration_map";
+ private static final String UID_OWNER_MAP_PATH =
+ "/sys/fs/bpf/netd_shared/map_netd_uid_owner_map";
private static final U32 UID_RULES_CONFIGURATION_KEY = new U32(0);
private static BpfMap<U32, U32> sConfigurationMap = null;
+ // BpfMap for UID_OWNER_MAP_PATH. This map is not accessed by others.
+ private static BpfMap<U32, UidOwnerValue> sUidOwnerMap = null;
// LINT.IfChange(match_type)
- private static final long NO_MATCH = 0;
- private static final long HAPPY_BOX_MATCH = (1 << 0);
- private static final long PENALTY_BOX_MATCH = (1 << 1);
- private static final long DOZABLE_MATCH = (1 << 2);
- private static final long STANDBY_MATCH = (1 << 3);
- private static final long POWERSAVE_MATCH = (1 << 4);
- private static final long RESTRICTED_MATCH = (1 << 5);
- private static final long LOW_POWER_STANDBY_MATCH = (1 << 6);
- private static final long IIF_MATCH = (1 << 7);
- private static final long LOCKDOWN_VPN_MATCH = (1 << 8);
- private static final long OEM_DENY_1_MATCH = (1 << 9);
- private static final long OEM_DENY_2_MATCH = (1 << 10);
- private static final long OEM_DENY_3_MATCH = (1 << 11);
+ @VisibleForTesting public static final long NO_MATCH = 0;
+ @VisibleForTesting public static final long HAPPY_BOX_MATCH = (1 << 0);
+ @VisibleForTesting public static final long PENALTY_BOX_MATCH = (1 << 1);
+ @VisibleForTesting public static final long DOZABLE_MATCH = (1 << 2);
+ @VisibleForTesting public static final long STANDBY_MATCH = (1 << 3);
+ @VisibleForTesting public static final long POWERSAVE_MATCH = (1 << 4);
+ @VisibleForTesting public static final long RESTRICTED_MATCH = (1 << 5);
+ @VisibleForTesting public static final long LOW_POWER_STANDBY_MATCH = (1 << 6);
+ @VisibleForTesting public static final long IIF_MATCH = (1 << 7);
+ @VisibleForTesting public static final long LOCKDOWN_VPN_MATCH = (1 << 8);
+ @VisibleForTesting public static final long OEM_DENY_1_MATCH = (1 << 9);
+ @VisibleForTesting public static final long OEM_DENY_2_MATCH = (1 << 10);
+ @VisibleForTesting public static final long OEM_DENY_3_MATCH = (1 << 11);
// LINT.ThenChange(packages/modules/Connectivity/bpf_progs/bpf_shared.h)
// TODO: Use Java BpfMap instead of JNI code (TrafficController) for map update.
@@ -99,11 +110,46 @@
}
/**
- * Only tests or BpfNetMaps#ensureInitialized can call this function.
+ * Set configurationMap for test.
*/
@VisibleForTesting
- public static void initialize(final Dependencies deps) {
- sConfigurationMap = deps.getConfigurationMap();
+ public static void setConfigurationMapForTest(BpfMap<U32, U32> configurationMap) {
+ sConfigurationMap = configurationMap;
+ }
+
+ /**
+ * Set uidOwnerMap for test.
+ */
+ @VisibleForTesting
+ public static void setUidOwnerMapForTest(BpfMap<U32, UidOwnerValue> uidOwnerMap) {
+ sUidOwnerMap = uidOwnerMap;
+ }
+
+ private static BpfMap<U32, U32> getConfigurationMap() {
+ try {
+ return new BpfMap<>(
+ CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, U32.class);
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Cannot open netd configuration map", e);
+ }
+ }
+
+ private static BpfMap<U32, UidOwnerValue> getUidOwnerMap() {
+ try {
+ return new BpfMap<>(
+ UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, UidOwnerValue.class);
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Cannot open uid owner map", e);
+ }
+ }
+
+ private static void setBpfMaps() {
+ if (sConfigurationMap == null) {
+ sConfigurationMap = getConfigurationMap();
+ }
+ if (sUidOwnerMap == null) {
+ sUidOwnerMap = getUidOwnerMap();
+ }
}
/**
@@ -112,33 +158,11 @@
*/
private static synchronized void ensureInitialized() {
if (sInitialized) return;
- if (!PRE_T) {
- System.loadLibrary("service-connectivity");
- native_init();
- initialize(new Dependencies());
- }
+ setBpfMaps();
+ native_init();
sInitialized = true;
}
- /**
- * Dependencies of BpfNetMaps, for injection in tests.
- */
- @VisibleForTesting
- public static class Dependencies {
- /**
- * Get configuration BPF map.
- */
- public BpfMap<U32, U32> getConfigurationMap() {
- try {
- return new BpfMap<>(
- CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, U32.class);
- } catch (ErrnoException e) {
- Log.e(TAG, "Cannot open netd configuration map: " + e);
- return null;
- }
- }
- }
-
/** Constructor used after T that doesn't need to use netd anymore. */
public BpfNetMaps() {
this(null);
@@ -147,7 +171,9 @@
}
public BpfNetMaps(final INetd netd) {
- ensureInitialized();
+ if (!PRE_T) {
+ ensureInitialized();
+ }
mNetd = netd;
}
@@ -175,6 +201,67 @@
}
}
+ private void removeRule(final int uid, final long match, final String caller) {
+ try {
+ synchronized (sUidOwnerMap) {
+ final UidOwnerValue oldMatch = sUidOwnerMap.getValue(new U32(uid));
+
+ if (oldMatch == null) {
+ throw new ServiceSpecificException(ENOENT,
+ "sUidOwnerMap does not have entry for uid: " + uid);
+ }
+
+ final UidOwnerValue newMatch = new UidOwnerValue(
+ (match == IIF_MATCH) ? 0 : oldMatch.iif,
+ oldMatch.rule & ~match
+ );
+
+ if (newMatch.rule == 0) {
+ sUidOwnerMap.deleteEntry(new U32(uid));
+ } else {
+ sUidOwnerMap.updateEntry(new U32(uid), newMatch);
+ }
+ }
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno,
+ caller + " failed to remove rule: " + Os.strerror(e.errno));
+ }
+ }
+
+ private void addRule(final int uid, final long match, final long iif, final String caller) {
+ if (match != IIF_MATCH && iif != 0) {
+ throw new ServiceSpecificException(EINVAL,
+ "Non-interface match must have zero interface index");
+ }
+
+ try {
+ synchronized (sUidOwnerMap) {
+ final UidOwnerValue oldMatch = sUidOwnerMap.getValue(new U32(uid));
+
+ final UidOwnerValue newMatch;
+ if (oldMatch != null) {
+ newMatch = new UidOwnerValue(
+ (match == IIF_MATCH) ? iif : oldMatch.iif,
+ oldMatch.rule | match
+ );
+ } else {
+ newMatch = new UidOwnerValue(
+ iif,
+ match
+ );
+ }
+ sUidOwnerMap.updateEntry(new U32(uid), newMatch);
+ }
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno,
+ caller + " failed to add rule: " + Os.strerror(e.errno));
+ }
+ }
+
+ private void addRule(final int uid, final long match, final String caller) {
+ addRule(uid, match, 0 /* iif */, caller);
+ }
+
/**
* Add naughty app bandwidth rule for specific app
*
@@ -183,8 +270,8 @@
* cause of the failure.
*/
public void addNaughtyApp(final int uid) {
- final int err = native_addNaughtyApp(uid);
- maybeThrow(err, "Unable to add naughty app");
+ throwIfPreT("addNaughtyApp is not available on pre-T devices");
+ addRule(uid, PENALTY_BOX_MATCH, "addNaughtyApp");
}
/**
@@ -195,8 +282,8 @@
* cause of the failure.
*/
public void removeNaughtyApp(final int uid) {
- final int err = native_removeNaughtyApp(uid);
- maybeThrow(err, "Unable to remove naughty app");
+ throwIfPreT("removeNaughtyApp is not available on pre-T devices");
+ removeRule(uid, PENALTY_BOX_MATCH, "removeNaughtyApp");
}
/**
@@ -207,8 +294,10 @@
* cause of the failure.
*/
public void addNiceApp(final int uid) {
- final int err = native_addNiceApp(uid);
- maybeThrow(err, "Unable to add nice app");
+ synchronized (sUidOwnerMap) {
+ final int err = native_addNiceApp(uid);
+ maybeThrow(err, "Unable to add nice app");
+ }
}
/**
@@ -219,8 +308,10 @@
* cause of the failure.
*/
public void removeNiceApp(final int uid) {
- final int err = native_removeNiceApp(uid);
- maybeThrow(err, "Unable to remove nice app");
+ synchronized (sUidOwnerMap) {
+ final int err = native_removeNiceApp(uid);
+ maybeThrow(err, "Unable to remove nice app");
+ }
}
/**
@@ -239,11 +330,6 @@
try {
synchronized (sUidRulesConfigBpfMapLock) {
final U32 config = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
- if (config == null) {
- throw new ServiceSpecificException(ENOENT,
- "Unable to get firewall chain status: sConfigurationMap does not have"
- + " entry for UID_RULES_CONFIGURATION_KEY");
- }
final long newConfig = enable ? (config.val | match) : (config.val & ~match);
sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(newConfig));
}
@@ -268,11 +354,6 @@
final long match = getMatchByFirewallChain(childChain);
try {
final U32 config = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY);
- if (config == null) {
- throw new ServiceSpecificException(ENOENT,
- "Unable to get firewall chain status: sConfigurationMap does not have"
- + " entry for UID_RULES_CONFIGURATION_KEY");
- }
return (config.val & match) != 0;
} catch (ErrnoException e) {
throw new ServiceSpecificException(e.errno,
@@ -295,11 +376,13 @@
*/
public int replaceUidChain(final String chainName, final boolean isAllowlist,
final int[] uids) {
- final int err = native_replaceUidChain(chainName, isAllowlist, uids);
- if (err != 0) {
- Log.e(TAG, "replaceUidChain failed: " + Os.strerror(-err));
+ synchronized (sUidOwnerMap) {
+ final int err = native_replaceUidChain(chainName, isAllowlist, uids);
+ if (err != 0) {
+ Log.e(TAG, "replaceUidChain failed: " + Os.strerror(-err));
+ }
+ return -err;
}
- return -err;
}
/**
@@ -312,8 +395,10 @@
* cause of the failure.
*/
public void setUidRule(final int childChain, final int uid, final int firewallRule) {
- final int err = native_setUidRule(childChain, uid, firewallRule);
- maybeThrow(err, "Unable to set uid rule");
+ synchronized (sUidOwnerMap) {
+ final int err = native_setUidRule(childChain, uid, firewallRule);
+ maybeThrow(err, "Unable to set uid rule");
+ }
}
/**
@@ -338,8 +423,10 @@
mNetd.firewallAddUidInterfaceRules(ifName, uids);
return;
}
- final int err = native_addUidInterfaceRules(ifName, uids);
- maybeThrow(err, "Unable to add uid interface rules");
+ synchronized (sUidOwnerMap) {
+ final int err = native_addUidInterfaceRules(ifName, uids);
+ maybeThrow(err, "Unable to add uid interface rules");
+ }
}
/**
@@ -358,8 +445,10 @@
mNetd.firewallRemoveUidInterfaceRules(uids);
return;
}
- final int err = native_removeUidInterfaceRules(uids);
- maybeThrow(err, "Unable to remove uid interface rules");
+ synchronized (sUidOwnerMap) {
+ final int err = native_removeUidInterfaceRules(uids);
+ maybeThrow(err, "Unable to remove uid interface rules");
+ }
}
/**
@@ -371,8 +460,10 @@
* cause of the failure.
*/
public void updateUidLockdownRule(final int uid, final boolean add) {
- final int err = native_updateUidLockdownRule(uid, add);
- maybeThrow(err, "Unable to update lockdown rule");
+ synchronized (sUidOwnerMap) {
+ final int err = native_updateUidLockdownRule(uid, add);
+ maybeThrow(err, "Unable to update lockdown rule");
+ }
}
/**
@@ -422,14 +513,23 @@
}
private static native void native_init();
+ @GuardedBy("sUidOwnerMap")
private native int native_addNaughtyApp(int uid);
+ @GuardedBy("sUidOwnerMap")
private native int native_removeNaughtyApp(int uid);
+ @GuardedBy("sUidOwnerMap")
private native int native_addNiceApp(int uid);
+ @GuardedBy("sUidOwnerMap")
private native int native_removeNiceApp(int uid);
+ @GuardedBy("sUidOwnerMap")
private native int native_replaceUidChain(String name, boolean isAllowlist, int[] uids);
+ @GuardedBy("sUidOwnerMap")
private native int native_setUidRule(int childChain, int uid, int firewallRule);
+ @GuardedBy("sUidOwnerMap")
private native int native_addUidInterfaceRules(String ifName, int[] uids);
+ @GuardedBy("sUidOwnerMap")
private native int native_removeUidInterfaceRules(int[] uids);
+ @GuardedBy("sUidOwnerMap")
private native int native_updateUidLockdownRule(int uid, boolean add);
private native int native_swapActiveStatsMap();
private native void native_setPermissionForUids(int permissions, int[] uids);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index b210bb3..37fc391 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -3422,6 +3422,17 @@
pw.increaseIndent();
mNetworkActivityTracker.dump(pw);
pw.decreaseIndent();
+
+ // pre-T is logged by netd.
+ if (SdkLevel.isAtLeastT()) {
+ pw.println();
+ pw.println("BPF programs & maps:");
+ pw.increaseIndent();
+ // Flush is required. Otherwise, the traces in fd can interleave with traces in pw.
+ pw.flush();
+ dumpTrafficController(pw, fd, /*verbose=*/ true);
+ pw.decreaseIndent();
+ }
}
private void dumpNetworks(IndentingPrintWriter pw) {
@@ -6352,7 +6363,7 @@
if (null != satisfier) {
// If the old NRI was satisfied by an NAI, then it may have had an active request.
// The active request is necessary to figure out what callbacks to send, in
- // particular then a network updates its capabilities.
+ // particular when a network updates its capabilities.
// As this code creates a new NRI with a new set of requests, figure out which of
// the list of requests should be the active request. It is always the first
// request of the list that can be satisfied by the satisfier since the order of
diff --git a/service/src/com/android/server/UidOwnerValue.java b/service/src/com/android/server/UidOwnerValue.java
new file mode 100644
index 0000000..f89e354
--- /dev/null
+++ b/service/src/com/android/server/UidOwnerValue.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import com.android.net.module.util.Struct;
+
+/** Value type for per uid traffic control configuration map */
+public class UidOwnerValue extends Struct {
+ // Allowed interface index. Only applicable if IIF_MATCH is set in the rule bitmask below.
+ @Field(order = 0, type = Type.U32)
+ public final long iif;
+
+ // A bitmask of match type.
+ @Field(order = 1, type = Type.U32)
+ public final long rule;
+
+ public UidOwnerValue(final long iif, final long rule) {
+ this.iif = iif;
+ this.rule = rule;
+ }
+}
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 498cf63..5ea586a 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -347,6 +347,19 @@
&& this.pid == that.pid
&& this.cookie == that.cookie;
}
+
+ @Override
+ public String toString() {
+ return "iface: " + iface
+ + " (" + ifIndex + ")"
+ + ", v4iface: " + v4iface
+ + " (" + v4ifIndex + ")"
+ + ", v4: " + v4
+ + ", v6: " + v6
+ + ", pfx96: " + pfx96
+ + ", pid: " + pid
+ + ", cookie: " + cookie;
+ }
};
@VisibleForTesting
@@ -819,9 +832,9 @@
* @param pw print writer.
*/
public void dump(@NonNull IndentingPrintWriter pw) {
- // TODO: dump ClatdTracker
// TODO: move map dump to a global place to avoid duplicate dump while there are two or
// more IPv6 only networks.
+ pw.println("CLAT tracker: " + mClatdTracker.toString());
pw.println("Forwarding rules:");
pw.increaseIndent();
dumpBpfIngress(pw);
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 34c6d2d..fd1ed60 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -51,7 +51,6 @@
import android.net.INetd;
import android.net.UidRange;
import android.net.Uri;
-import android.net.util.SharedLog;
import android.os.Build;
import android.os.Process;
import android.os.RemoteException;
@@ -70,6 +69,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.ProcessShimImpl;
import com.android.networkstack.apishim.common.ProcessShim;
import com.android.server.BpfNetMaps;
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 458d225..1748612 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -49,7 +49,6 @@
import android.os.Handler
import android.os.Looper
import android.os.OutcomeReceiver
-import android.os.SystemProperties
import android.platform.test.annotations.AppModeFull
import android.util.ArraySet
import androidx.test.platform.app.InstrumentationRegistry
@@ -69,13 +68,13 @@
import com.android.testutils.waitForIdle
import org.junit.After
import org.junit.Assume.assumeTrue
-import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.net.Inet6Address
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeoutException
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@@ -206,12 +205,8 @@
fun eventuallyExpect(expected: CallbackEntry) = events.poll(TIMEOUT_MS) { it == expected }
- fun eventuallyExpect(interfaceName: String, state: Int, role: Int) {
- assertNotNull(eventuallyExpect(createChangeEvent(interfaceName, state, role)))
- }
-
fun eventuallyExpect(iface: EthernetTestInterface, state: Int, role: Int) {
- eventuallyExpect(iface.name, state, role)
+ assertNotNull(eventuallyExpect(createChangeEvent(iface.name, state, role)))
}
fun assertNoCallback() {
@@ -458,32 +453,45 @@
}
}
- // TODO: this function is now used in two places (EthernetManagerTest and
- // EthernetTetheringTest), so it should be moved to testutils.
- private fun isAdbOverNetwork(): Boolean {
- // If adb TCP port opened, this test may running by adb over network.
- return (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1 ||
- SystemProperties.getInt("service.adb.tcp.port", -1) > -1)
+ private fun assumeNoInterfaceForTetheringAvailable() {
+ // Interfaces that have configured NetworkCapabilities will never be used for tethering,
+ // see aosp/2123900.
+ try {
+ // assumeException does not exist.
+ requestTetheredInterface().expectOnAvailable()
+ // interface used for tethering is available, throw an assumption error.
+ assumeTrue(false)
+ } catch (e: TimeoutException) {
+ // do nothing -- the TimeoutException indicates that no interface is available for
+ // tethering.
+ releaseTetheredInterface()
+ }
}
@Test
fun testCallbacks_forServerModeInterfaces() {
- // do not run this test when adb might be connected over ethernet.
- assumeFalse(isAdbOverNetwork())
+ // do not run this test if an interface that can be used for tethering already exists.
+ assumeNoInterfaceForTetheringAvailable()
+
+ val iface = createInterface()
+ requestTetheredInterface().expectOnAvailable()
val listener = EthernetStateListener()
addInterfaceStateListener(listener)
-
- // it is possible that a physical interface is present, so it is not guaranteed that iface
- // will be put into server mode. This should not matter for the test though. Calling
- // createInterface() makes sure we have at least one interface available.
- val iface = createInterface()
- val cb = requestTetheredInterface()
- val ifaceName = cb.expectOnAvailable()
- listener.eventuallyExpect(ifaceName, STATE_LINK_UP, ROLE_SERVER)
+ // TODO(b/236895792): THIS IS A BUG! Existing server mode interfaces are not reported when
+ // an InterfaceStateListener is registered.
+ // Note: using eventuallyExpect as there may be other interfaces present.
+ // listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_SERVER)
releaseTetheredInterface()
- listener.eventuallyExpect(ifaceName, STATE_LINK_UP, ROLE_CLIENT)
+ listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
+
+ requestTetheredInterface().expectOnAvailable()
+ // This should be changed to expectCallback, once b/236895792 is fixed.
+ listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_SERVER)
+
+ releaseTetheredInterface()
+ listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
}
/**
@@ -651,4 +659,26 @@
iface.setCarrierEnabled(false)
cb.eventuallyExpectLost()
}
+
+ @Test
+ fun testRemoveInterface_whileInServerMode() {
+ assumeNoInterfaceForTetheringAvailable()
+
+ val listener = EthernetStateListener()
+ addInterfaceStateListener(listener)
+
+ val iface = createInterface()
+ val ifaceName = requestTetheredInterface().expectOnAvailable()
+
+ assertEquals(iface.name, ifaceName)
+ listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_SERVER)
+
+ removeInterface(iface)
+
+ // Note: removeInterface already verifies that a STATE_ABSENT, ROLE_NONE callback is
+ // received, but it can't hurt to explicitly check for it.
+ listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
+ releaseTetheredInterface()
+ listener.assertNoCallback()
+ }
}
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 80338aa..efc24d3 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -47,6 +47,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.android.connectivity.resources.R
+import com.android.server.BpfNetMaps
import com.android.server.ConnectivityService
import com.android.server.NetworkAgentWrapper
import com.android.server.TestNetIdManager
@@ -208,6 +209,7 @@
doReturn(mock(ProxyTracker::class.java)).`when`(deps).makeProxyTracker(any(), any())
doReturn(mock(MockableSystemProperties::class.java)).`when`(deps).systemProperties
doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager()
+ doReturn(mock(BpfNetMaps::class.java)).`when`(deps).getBpfNetMaps(any())
doAnswer { inv ->
object : MultinetworkPolicyTracker(inv.getArgument(0), inv.getArgument(1),
inv.getArgument(2)) {
diff --git a/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
index c7cf040..361c968 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -22,8 +22,8 @@
import android.net.INetworkMonitorCallbacks
import android.net.Network
import android.net.metrics.IpConnectivityLog
-import android.net.util.SharedLog
import android.os.IBinder
+import com.android.net.module.util.SharedLog
import com.android.networkstack.netlink.TcpSocketTracker
import com.android.server.NetworkStackService
import com.android.server.NetworkStackService.NetworkMonitorConnector
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 634ec9c..e59fbc2 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -26,8 +26,16 @@
import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
import static android.net.INetd.PERMISSION_INTERNET;
+import static com.android.server.BpfNetMaps.DOZABLE_MATCH;
+import static com.android.server.BpfNetMaps.IIF_MATCH;
+import static com.android.server.BpfNetMaps.NO_MATCH;
+import static com.android.server.BpfNetMaps.PENALTY_BOX_MATCH;
+import static com.android.server.BpfNetMaps.POWERSAVE_MATCH;
+import static com.android.server.BpfNetMaps.RESTRICTED_MATCH;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
@@ -68,7 +76,9 @@
private static final int TEST_UID = 10086;
private static final int[] TEST_UIDS = {10002, 10003};
- private static final String IFNAME = "wlan0";
+ private static final String TEST_IF_NAME = "wlan0";
+ private static final int TEST_IF_INDEX = 7;
+ private static final int NO_IIF = 0;
private static final String CHAINNAME = "fw_dozable";
private static final U32 UID_RULES_CONFIGURATION_KEY = new U32(0);
private static final List<Integer> FIREWALL_CHAINS = List.of(
@@ -85,31 +95,23 @@
private BpfNetMaps mBpfNetMaps;
@Mock INetd mNetd;
- private static final TestBpfMap<U32, U32> sConfigurationMap =
- new TestBpfMap<>(U32.class, U32.class);
+ private final BpfMap<U32, U32> mConfigurationMap = new TestBpfMap<>(U32.class, U32.class);
+ private final BpfMap<U32, UidOwnerValue> mUidOwnerMap =
+ new TestBpfMap<>(U32.class, UidOwnerValue.class);
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ BpfNetMaps.setConfigurationMapForTest(mConfigurationMap);
+ BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
mBpfNetMaps = new BpfNetMaps(mNetd);
- BpfNetMaps.initialize(makeDependencies());
- sConfigurationMap.clear();
- }
-
- private static BpfNetMaps.Dependencies makeDependencies() {
- return new BpfNetMaps.Dependencies() {
- @Override
- public BpfMap<U32, U32> getConfigurationMap() {
- return sConfigurationMap;
- }
- };
}
@Test
public void testBpfNetMapsBeforeT() throws Exception {
assumeFalse(SdkLevel.isAtLeastT());
- mBpfNetMaps.addUidInterfaceRules(IFNAME, TEST_UIDS);
- verify(mNetd).firewallAddUidInterfaceRules(IFNAME, TEST_UIDS);
+ mBpfNetMaps.addUidInterfaceRules(TEST_IF_NAME, TEST_UIDS);
+ verify(mNetd).firewallAddUidInterfaceRules(TEST_IF_NAME, TEST_UIDS);
mBpfNetMaps.removeUidInterfaceRules(TEST_UIDS);
verify(mNetd).firewallRemoveUidInterfaceRules(TEST_UIDS);
mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
@@ -121,7 +123,7 @@
for (final int chain: enableChains) {
match |= mBpfNetMaps.getMatchByFirewallChain(chain);
}
- sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(match));
+ mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(match));
for (final int chain: FIREWALL_CHAINS) {
final String testCase = "EnabledChains: " + enableChains + " CheckedChain: " + chain;
@@ -175,14 +177,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.S_V2)
- public void testIsChainEnabledMissingConfiguration() {
- // sConfigurationMap does not have entry for UID_RULES_CONFIGURATION_KEY
- assertThrows(ServiceSpecificException.class,
- () -> mBpfNetMaps.isChainEnabled(FIREWALL_CHAIN_DOZABLE));
- }
-
- @Test
@IgnoreAfter(Build.VERSION_CODES.S_V2)
public void testIsChainEnabledBeforeT() {
assertThrows(UnsupportedOperationException.class,
@@ -195,17 +189,17 @@
expectedMatch |= mBpfNetMaps.getMatchByFirewallChain(chain);
}
- assertEquals(0, sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
+ assertEquals(0, mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
for (final int chain: testChains) {
mBpfNetMaps.setChildChain(chain, true /* enable */);
}
- assertEquals(expectedMatch, sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
+ assertEquals(expectedMatch, mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
for (final int chain: testChains) {
mBpfNetMaps.setChildChain(chain, false /* enable */);
}
- assertEquals(0, sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
+ assertEquals(0, mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
}
private void doTestSetChildChain(final int testChain) throws Exception {
@@ -215,7 +209,7 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testSetChildChain() throws Exception {
- sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
+ mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
doTestSetChildChain(FIREWALL_CHAIN_DOZABLE);
doTestSetChildChain(FIREWALL_CHAIN_STANDBY);
doTestSetChildChain(FIREWALL_CHAIN_POWERSAVE);
@@ -229,7 +223,7 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testSetChildChainMultipleChain() throws Exception {
- sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
+ mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
doTestSetChildChain(List.of(
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_STANDBY));
@@ -252,17 +246,90 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.S_V2)
- public void testSetChildChainMissingConfiguration() {
- // sConfigurationMap does not have entry for UID_RULES_CONFIGURATION_KEY
- assertThrows(ServiceSpecificException.class,
- () -> mBpfNetMaps.setChildChain(FIREWALL_CHAIN_DOZABLE, true /* enable */));
- }
-
- @Test
@IgnoreAfter(Build.VERSION_CODES.S_V2)
public void testSetChildChainBeforeT() {
assertThrows(UnsupportedOperationException.class,
() -> mBpfNetMaps.setChildChain(FIREWALL_CHAIN_DOZABLE, true /* enable */));
}
+
+ private void checkUidOwnerValue(final long uid, final long expectedIif,
+ final long expectedMatch) throws Exception {
+ final UidOwnerValue config = mUidOwnerMap.getValue(new U32(uid));
+ if (expectedMatch == 0) {
+ assertNull(config);
+ } else {
+ assertEquals(expectedIif, config.iif);
+ assertEquals(expectedMatch, config.rule);
+ }
+ }
+
+ private void doTestRemoveNaughtyApp(final long iif, final long match) throws Exception {
+ mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(iif, match));
+
+ mBpfNetMaps.removeNaughtyApp(TEST_UID);
+
+ checkUidOwnerValue(TEST_UID, iif, match & ~PENALTY_BOX_MATCH);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testRemoveNaughtyApp() throws Exception {
+ doTestRemoveNaughtyApp(NO_IIF, PENALTY_BOX_MATCH);
+
+ // PENALTY_BOX_MATCH with other matches
+ doTestRemoveNaughtyApp(NO_IIF, PENALTY_BOX_MATCH | DOZABLE_MATCH | POWERSAVE_MATCH);
+
+ // PENALTY_BOX_MATCH with IIF_MATCH
+ doTestRemoveNaughtyApp(TEST_IF_INDEX, PENALTY_BOX_MATCH | IIF_MATCH);
+
+ // PENALTY_BOX_MATCH is not enabled
+ doTestRemoveNaughtyApp(NO_IIF, DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testRemoveNaughtyAppMissingUid() {
+ // UidOwnerMap does not have entry for TEST_UID
+ assertThrows(ServiceSpecificException.class,
+ () -> mBpfNetMaps.removeNaughtyApp(TEST_UID));
+ }
+
+ @Test
+ @IgnoreAfter(Build.VERSION_CODES.S_V2)
+ public void testRemoveNaughtyAppBeforeT() {
+ assertThrows(UnsupportedOperationException.class,
+ () -> mBpfNetMaps.removeNaughtyApp(TEST_UID));
+ }
+
+ private void doTestAddNaughtyApp(final long iif, final long match) throws Exception {
+ if (match != NO_MATCH) {
+ mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(iif, match));
+ }
+
+ mBpfNetMaps.addNaughtyApp(TEST_UID);
+
+ checkUidOwnerValue(TEST_UID, iif, match | PENALTY_BOX_MATCH);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testAddNaughtyApp() throws Exception {
+ doTestAddNaughtyApp(NO_IIF, NO_MATCH);
+
+ // Other matches are enabled
+ doTestAddNaughtyApp(NO_IIF, DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH);
+
+ // IIF_MATCH is enabled
+ doTestAddNaughtyApp(TEST_IF_INDEX, IIF_MATCH);
+
+ // PENALTY_BOX_MATCH is already enabled
+ doTestAddNaughtyApp(NO_IIF, PENALTY_BOX_MATCH | DOZABLE_MATCH);
+ }
+
+ @Test
+ @IgnoreAfter(Build.VERSION_CODES.S_V2)
+ public void testAddNaughtyAppBeforeT() {
+ assertThrows(UnsupportedOperationException.class,
+ () -> mBpfNetMaps.addNaughtyApp(TEST_UID));
+ }
}
diff --git a/tests/unit/java/com/android/server/VpnManagerServiceTest.java b/tests/unit/java/com/android/server/VpnManagerServiceTest.java
index ece13b3..c814cc5 100644
--- a/tests/unit/java/com/android/server/VpnManagerServiceTest.java
+++ b/tests/unit/java/com/android/server/VpnManagerServiceTest.java
@@ -24,7 +24,6 @@
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -32,15 +31,18 @@
import static org.mockito.Mockito.verify;
import android.annotation.UserIdInt;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.INetd;
+import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.INetworkManagementService;
import android.os.Looper;
+import android.os.UserHandle;
import android.os.UserManager;
import androidx.test.filters.SmallTest;
@@ -48,14 +50,15 @@
import com.android.server.connectivity.Vpn;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.HandlerUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
@RunWith(DevSdkIgnoreRunner.class)
@IgnoreUpTo(R) // VpnManagerService is not available before R
@@ -64,18 +67,23 @@
@Rule
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
- @Spy Context mContext;
+ private static final int TIMEOUT_MS = 2_000;
+
+ @Mock Context mContext;
+ @Mock Context mSystemContext;
+ @Mock Context mUserAllContext;
private HandlerThread mHandlerThread;
- @Mock private Handler mHandler;
@Mock private Vpn mVpn;
@Mock private INetworkManagementService mNms;
@Mock private ConnectivityManager mCm;
@Mock private UserManager mUserManager;
@Mock private INetd mNetd;
@Mock private PackageManager mPackageManager;
+
private VpnManagerServiceDependencies mDeps;
private VpnManagerService mService;
-
+ private BroadcastReceiver mUserPresentReceiver;
+ private BroadcastReceiver mIntentReceiver;
private final String mNotMyVpnPkg = "com.not.my.vpn";
class VpnManagerServiceDependencies extends VpnManagerService.Dependencies {
@@ -107,46 +115,54 @@
mHandlerThread = new HandlerThread("TestVpnManagerService");
mDeps = new VpnManagerServiceDependencies();
- doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
+ doReturn(mUserAllContext).when(mContext).createContextAsUser(UserHandle.ALL, 0);
+ doReturn(mSystemContext).when(mContext).createContextAsUser(UserHandle.SYSTEM, 0);
doReturn(mPackageManager).when(mContext).getPackageManager();
setMockedPackages(mPackageManager, sPackages);
mockService(mContext, ConnectivityManager.class, Context.CONNECTIVITY_SERVICE, mCm);
mockService(mContext, UserManager.class, Context.USER_SERVICE, mUserManager);
-
- doReturn(new Intent()).when(mContext).registerReceiver(
- any() /* receiver */,
- any() /* intentFilter */,
- any() /* broadcastPermission */,
- eq(mHandler) /* scheduler */);
doReturn(SYSTEM_USER).when(mUserManager).getUserInfo(eq(SYSTEM_USER_ID));
+
mService = new VpnManagerService(mContext, mDeps);
+ mService.systemReady();
+
+ final ArgumentCaptor<BroadcastReceiver> intentReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ final ArgumentCaptor<BroadcastReceiver> userPresentReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mSystemContext).registerReceiver(
+ userPresentReceiverCaptor.capture(), any(), any(), any());
+ verify(mUserAllContext, times(2)).registerReceiver(
+ intentReceiverCaptor.capture(), any(), any(), any());
+ mUserPresentReceiver = userPresentReceiverCaptor.getValue();
+ mIntentReceiver = intentReceiverCaptor.getValue();
+
+ // Add user to create vpn in mVpn
+ onUserStarted(SYSTEM_USER_ID);
+ assertNotNull(mService.mVpns.get(SYSTEM_USER_ID));
}
@Test
public void testUpdateAppExclusionList() {
- // Add user to create vpn in mVpn
- mService.onUserStarted(SYSTEM_USER_ID);
- assertNotNull(mService.mVpns.get(SYSTEM_USER_ID));
-
// Start vpn
mService.startVpnProfile(TEST_VPN_PKG);
verify(mVpn).startVpnProfile(eq(TEST_VPN_PKG));
// Remove package due to package replaced.
- mService.onPackageRemoved(PKGS[0], PKG_UIDS[0], true /* isReplacing */);
+ onPackageRemoved(PKGS[0], PKG_UIDS[0], true /* isReplacing */);
verify(mVpn, never()).refreshPlatformVpnAppExclusionList();
// Add package due to package replaced.
- mService.onPackageAdded(PKGS[0], PKG_UIDS[0], true /* isReplacing */);
+ onPackageAdded(PKGS[0], PKG_UIDS[0], true /* isReplacing */);
verify(mVpn, never()).refreshPlatformVpnAppExclusionList();
// Remove package
- mService.onPackageRemoved(PKGS[0], PKG_UIDS[0], false /* isReplacing */);
+ onPackageRemoved(PKGS[0], PKG_UIDS[0], false /* isReplacing */);
verify(mVpn).refreshPlatformVpnAppExclusionList();
// Add the package back
- mService.onPackageAdded(PKGS[0], PKG_UIDS[0], false /* isReplacing */);
+ onPackageAdded(PKGS[0], PKG_UIDS[0], false /* isReplacing */);
verify(mVpn, times(2)).refreshPlatformVpnAppExclusionList();
}
@@ -160,4 +176,69 @@
public void testStopVpnProfileFromDiffPackage() {
assertThrows(SecurityException.class, () -> mService.stopVpnProfile(mNotMyVpnPkg));
}
+
+ @Test
+ public void testGetProvisionedVpnProfileStateFromDiffPackage() {
+ assertThrows(SecurityException.class, () ->
+ mService.getProvisionedVpnProfileState(mNotMyVpnPkg));
+ }
+
+ @Test
+ public void testGetProvisionedVpnProfileState() {
+ mService.getProvisionedVpnProfileState(TEST_VPN_PKG);
+ verify(mVpn).getProvisionedVpnProfileState(TEST_VPN_PKG);
+ }
+
+ private Intent buildIntent(String action, String packageName, int userId, int uid,
+ boolean isReplacing) {
+ final Intent intent = new Intent(action);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ intent.putExtra(Intent.EXTRA_UID, uid);
+ intent.putExtra(Intent.EXTRA_REPLACING, isReplacing);
+ if (packageName != null) {
+ intent.setData(Uri.fromParts("package" /* scheme */, packageName, null /* fragment */));
+ }
+
+ return intent;
+ }
+
+ private void sendIntent(Intent intent) {
+ final Handler h = mHandlerThread.getThreadHandler();
+
+ // Send in handler thread.
+ h.post(() -> mIntentReceiver.onReceive(mContext, intent));
+ HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
+ }
+
+ private void onUserStarted(int userId) {
+ sendIntent(buildIntent(Intent.ACTION_USER_STARTED,
+ null /* packageName */, userId, -1 /* uid */, false /* isReplacing */));
+ }
+
+ private void onPackageAdded(String packageName, int userId, int uid, boolean isReplacing) {
+ sendIntent(buildIntent(Intent.ACTION_PACKAGE_ADDED, packageName, userId, uid, isReplacing));
+ }
+
+ private void onPackageAdded(String packageName, int uid, boolean isReplacing) {
+ onPackageAdded(packageName, UserHandle.USER_SYSTEM, uid, isReplacing);
+ }
+
+ private void onPackageRemoved(String packageName, int userId, int uid, boolean isReplacing) {
+ sendIntent(buildIntent(Intent.ACTION_PACKAGE_REMOVED, packageName, userId, uid,
+ isReplacing));
+ }
+
+ private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
+ onPackageRemoved(packageName, UserHandle.USER_SYSTEM, uid, isReplacing);
+ }
+
+ @Test
+ public void testReceiveIntentFromNonHandlerThread() {
+ assertThrows(IllegalStateException.class, () ->
+ mIntentReceiver.onReceive(mContext, buildIntent(Intent.ACTION_PACKAGE_REMOVED,
+ PKGS[0], UserHandle.USER_SYSTEM, PKG_UIDS[0], true /* isReplacing */)));
+
+ assertThrows(IllegalStateException.class, () ->
+ mUserPresentReceiver.onReceive(mContext, new Intent(Intent.ACTION_USER_PRESENT)));
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index 8dfe924..feee293 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -525,16 +525,19 @@
coordinator.dump(ipw);
final String[] dumpStrings = stringWriter.toString().split("\n");
- assertEquals(5, dumpStrings.length);
- assertEquals("Forwarding rules:", dumpStrings[0].trim());
+ assertEquals(6, dumpStrings.length);
+ assertEquals("CLAT tracker: iface: test0 (1000), v4iface: v4-test0 (1001), "
+ + "v4: /192.0.0.46, v6: /2001:db8:0:b11::464, pfx96: /64:ff9b::, "
+ + "pid: 10483, cookie: 27149", dumpStrings[0].trim());
+ assertEquals("Forwarding rules:", dumpStrings[1].trim());
assertEquals("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif",
- dumpStrings[1].trim());
- assertEquals("1000 /64:ff9b::/96 /2001:db8:0:b11::464 -> /192.0.0.46 1001",
dumpStrings[2].trim());
- assertEquals("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif",
+ assertEquals("1000 /64:ff9b::/96 /2001:db8:0:b11::464 -> /192.0.0.46 1001",
dumpStrings[3].trim());
- assertEquals("1001 /192.0.0.46 -> /2001:db8:0:b11::464 /64:ff9b::/96 1000 ether",
+ assertEquals("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif",
dumpStrings[4].trim());
+ assertEquals("1001 /192.0.0.46 -> /2001:db8:0:b11::464 /64:ff9b::/96 1000 ether",
+ dumpStrings[5].trim());
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 0891ee3..5c1992d 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -232,7 +232,7 @@
private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345;
private static final long TEST_TIMEOUT_MS = 500L;
private static final String PRIMARY_USER_APP_EXCLUDE_KEY =
- "VPN_APP_EXCLUDED_27_com.testvpn.vpn";
+ "VPNAPPEXCLUDED_27_com.testvpn.vpn";
static final String PKGS_BYTES = getPackageByteString(List.of(PKGS));
private static final Range<Integer> PRIMARY_USER_RANGE = uidRangeForUser(PRIMARY_USER.id);
@@ -1360,6 +1360,31 @@
}
@Test
+ public void testReconnectVpnManagerVpnWithAlwaysOnEnabled() throws Exception {
+ final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+ when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+ .thenReturn(mVpnProfile.encode());
+ vpn.startVpnProfile(TEST_VPN_PKG);
+ verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+
+ // Enable VPN always-on for TEST_VPN_PKG.
+ assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, false /* lockdown */,
+ null /* lockdownAllowlist */));
+
+ // Reset to verify next startVpnProfile.
+ reset(mAppOps);
+
+ vpn.stopVpnProfile(TEST_VPN_PKG);
+
+ // Reconnect the vpn with different package will cause exception.
+ assertThrows(SecurityException.class, () -> vpn.startVpnProfile(PKGS[0]));
+
+ // Reconnect the vpn again with the vpn always on package w/o exception.
+ vpn.startVpnProfile(TEST_VPN_PKG);
+ verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+ }
+
+ @Test
public void testSetPackageAuthorizationVpnService() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 5400a00..f6fb45c 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -44,6 +44,7 @@
import androidx.test.filters.SmallTest;
import com.android.frameworks.tests.net.R;
+import com.android.server.BpfNetMaps;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -74,6 +75,7 @@
private File mTestProc;
private NetworkStatsFactory mFactory;
@Mock private Context mContext;
+ @Mock private BpfNetMaps mBpfNetMaps;
@Before
public void setUp() throws Exception {
@@ -84,7 +86,7 @@
// applications. So in order to have a test support native library, the native code
// related to networkStatsFactory is compiled to a minimal native library and loaded here.
System.loadLibrary("networkstatsfactorytestjni");
- mFactory = new NetworkStatsFactory(mContext, mTestProc, false);
+ mFactory = new NetworkStatsFactory(mContext, mTestProc, false, mBpfNetMaps);
mFactory.updateUnderlyingNetworkInfos(new UnderlyingNetworkInfo[0]);
}