Merge "Remove unnecessary sleep workaround in test"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index c4c79c6..4eeaf51 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -34,6 +34,18 @@
}
]
},
+ // CTS tests that target older SDKs.
+ {
+ "name": "CtsNetTestCasesMaxTargetSdk31",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
{
"name": "bpf_existence_test"
},
@@ -41,6 +53,9 @@
"name": "connectivity_native_test"
},
{
+ "name": "libclat_test"
+ },
+ {
"name": "netd_updatable_unit_test"
},
{
@@ -68,16 +83,10 @@
"keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
},
{
- "name": "libclat_test"
- },
- {
"name": "traffic_controller_unit_test",
"keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
},
{
- "name": "libnetworkstats_test"
- },
- {
"name": "FrameworksNetDeflakeTest"
}
],
@@ -94,6 +103,17 @@
]
},
{
+ "name": "CtsNetTestCasesMaxTargetSdk31[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
+ {
"name": "bpf_existence_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
},
{
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 026ed54..adcc236 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -21,7 +21,7 @@
java_defaults {
name: "TetheringApiLevel",
sdk_version: "module_current",
- target_sdk_version: "31",
+ target_sdk_version: "33",
min_sdk_version: "30",
}
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index bb40935..76c5d5c 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -105,12 +105,6 @@
certificate: "com.android.tethering",
}
-filegroup {
- name: "connectivity-hiddenapi-files",
- srcs: ["hiddenapi/*.txt"],
- visibility: ["//packages/modules/Connectivity:__subpackages__"],
-}
-
// Encapsulate the contributions made by the com.android.tethering to the bootclasspath.
bootclasspath_fragment {
name: "com.android.tethering-bootclasspath-fragment",
diff --git a/Tethering/apex/manifest.json b/Tethering/apex/manifest.json
index 88f13b2..3cb03ed 100644
--- a/Tethering/apex/manifest.json
+++ b/Tethering/apex/manifest.json
@@ -1,4 +1,4 @@
{
"name": "com.android.tethering",
- "version": 319999900
+ "version": 339990000
}
diff --git a/Tethering/jarjar-rules.txt b/Tethering/jarjar-rules.txt
index 40eed3f..904e491 100644
--- a/Tethering/jarjar-rules.txt
+++ b/Tethering/jarjar-rules.txt
@@ -4,6 +4,7 @@
# module will be overwritten by the ones in the framework.
rule com.android.internal.util.** com.android.networkstack.tethering.util.@1
rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1
+rule android.util.IndentingPrintWriter* com.android.networkstack.tethering.util.AndroidUtilIndentingPrintWriter@1
rule android.net.shared.Inet4AddressUtils* com.android.networkstack.tethering.shared.Inet4AddressUtils@1
@@ -13,4 +14,7 @@
# Classes from net-utils-device-common
rule com.android.net.module.util.Struct* com.android.networkstack.tethering.util.Struct@1
-rule com.google.protobuf.** com.android.networkstack.tethering.protobuf@1
\ No newline at end of file
+rule com.google.protobuf.** com.android.networkstack.tethering.protobuf@1
+
+# Classes for hardware offload hidl interface
+rule android.hidl.base.V1_0.DebugInfo* com.android.networkstack.tethering.hidl.base.V1_0.DebugInfo@1
diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index cc2422f..41a10ae 100644
--- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -172,6 +172,9 @@
return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS);
}
+ // This ensures that tethering isn't started on 2 different interfaces with the same type.
+ // Once tethering could support multiple interface with the same type,
+ // TetheringSoftApCallback would need to handle it among others.
final LinkAddress cachedAddress = mCachedAddresses.get(ipServer.interfaceType());
if (useLastAddress && cachedAddress != null
&& !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 551fd63..af017f3 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -448,8 +448,22 @@
mStateReceiver, noUpstreamFilter, PERMISSION_MAINLINE_NETWORK_STACK, mHandler);
final WifiManager wifiManager = getWifiManager();
+ TetheringSoftApCallback softApCallback = new TetheringSoftApCallback();
if (wifiManager != null) {
- wifiManager.registerSoftApCallback(mExecutor, new TetheringSoftApCallback());
+ wifiManager.registerSoftApCallback(mExecutor, softApCallback);
+ }
+ if (SdkLevel.isAtLeastT() && wifiManager != null) {
+ try {
+ // Although WifiManager#registerLocalOnlyHotspotSoftApCallback document that it need
+ // NEARBY_WIFI_DEVICES permission, but actually a caller who have NETWORK_STACK
+ // or MAINLINE_NETWORK_STACK permission would also able to use this API.
+ wifiManager.registerLocalOnlyHotspotSoftApCallback(mExecutor, softApCallback);
+ } catch (UnsupportedOperationException e) {
+ // Since wifi module development in internal branch,
+ // #registerLocalOnlyHotspotSoftApCallback currently doesn't supported in AOSP
+ // before AOSP switch to Android T + 1.
+ Log.wtf(TAG, "registerLocalOnlyHotspotSoftApCallback API is not supported");
+ }
}
startTrackDefaultNetwork();
@@ -545,6 +559,13 @@
}
// Called by wifi when the number of soft AP clients changed.
+ // Currently multiple softAp would not behave well in PrivateAddressCoordinator
+ // (where it gets the address from cache), it ensure tethering only support one ipServer for
+ // TETHERING_WIFI. Once tethering support multiple softAp enabled simultaneously,
+ // onConnectedClientsChanged should also be updated to support tracking different softAp's
+ // clients individually.
+ // TODO: Add wtf log and have check to reject request duplicated type with different
+ // interface.
@Override
public void onConnectedClientsChanged(final List<WifiClient> clients) {
updateConnectedClients(clients);
@@ -1297,7 +1318,7 @@
// Finally bring up serving on the new interface
mWifiP2pTetherInterface = group.getInterface();
- enableWifiIpServing(mWifiP2pTetherInterface, IFACE_IP_MODE_LOCAL_ONLY);
+ enableWifiP2pIpServing(mWifiP2pTetherInterface);
}
private void handleUserRestrictionAction() {
@@ -1388,20 +1409,22 @@
changeInterfaceState(ifname, ipServingMode);
}
- private void disableWifiIpServingCommon(int tetheringType, String ifname, int apState) {
- mLog.log("Canceling WiFi tethering request -"
- + " type=" + tetheringType
- + " interface=" + ifname
- + " state=" + apState);
-
- if (!TextUtils.isEmpty(ifname)) {
- final TetherState ts = mTetherStates.get(ifname);
- if (ts != null) {
- ts.ipServer.unwanted();
- return;
- }
+ private void disableWifiIpServingCommon(int tetheringType, String ifname) {
+ if (!TextUtils.isEmpty(ifname) && mTetherStates.containsKey(ifname)) {
+ mTetherStates.get(ifname).ipServer.unwanted();
+ return;
}
+ if (SdkLevel.isAtLeastT()) {
+ mLog.e("Tethering no longer handle untracked interface after T: " + ifname);
+ return;
+ }
+
+ // Attempt to guess the interface name before T. Pure AOSP code should never enter here
+ // because WIFI_AP_STATE_CHANGED intent always include ifname and it should be tracked
+ // by mTetherStates. In case OEMs have some modification in wifi side which pass null
+ // or empty ifname. Before T, tethering allow to disable the first wifi ipServer if
+ // given ifname don't match any tracking ipServer.
for (int i = 0; i < mTetherStates.size(); i++) {
final IpServer ipServer = mTetherStates.valueAt(i).ipServer;
if (ipServer.interfaceType() == tetheringType) {
@@ -1409,7 +1432,6 @@
return;
}
}
-
mLog.log("Error disabling Wi-Fi IP serving; "
+ (TextUtils.isEmpty(ifname) ? "no interface name specified"
: "specified interface: " + ifname));
@@ -1418,20 +1440,39 @@
private void disableWifiIpServing(String ifname, int apState) {
// Regardless of whether we requested this transition, the AP has gone
// down. Don't try to tether again unless we're requested to do so.
- // TODO: Remove this altogether, once Wi-Fi reliably gives us an
- // interface name with every broadcast.
mWifiTetherRequested = false;
- disableWifiIpServingCommon(TETHERING_WIFI, ifname, apState);
+ mLog.log("Canceling WiFi tethering request - interface=" + ifname + " state=" + apState);
+
+ disableWifiIpServingCommon(TETHERING_WIFI, ifname);
+ }
+
+ private void enableWifiP2pIpServing(String ifname) {
+ if (TextUtils.isEmpty(ifname)) {
+ mLog.e("Cannot enable P2P IP serving with invalid interface");
+ return;
+ }
+
+ // After T, tethering always trust the iface pass by state change intent. This allow
+ // tethering to deprecate tetherable p2p regexs after T.
+ final int type = SdkLevel.isAtLeastT() ? TETHERING_WIFI_P2P : ifaceNameToType(ifname);
+ if (!checkTetherableType(type)) {
+ mLog.e(ifname + " is not a tetherable iface, ignoring");
+ return;
+ }
+ enableIpServing(type, ifname, IpServer.STATE_LOCAL_ONLY);
}
private void disableWifiP2pIpServingIfNeeded(String ifname) {
if (TextUtils.isEmpty(ifname)) return;
- disableWifiIpServingCommon(TETHERING_WIFI_P2P, ifname, /* fake */ 0);
+ mLog.log("Canceling P2P tethering request - interface=" + ifname);
+ disableWifiIpServingCommon(TETHERING_WIFI_P2P, ifname);
}
private void enableWifiIpServing(String ifname, int wifiIpMode) {
+ mLog.log("request WiFi tethering - interface=" + ifname + " state=" + wifiIpMode);
+
// Map wifiIpMode values to IpServer.Callback serving states, inferring
// from mWifiTetherRequested as a final "best guess".
final int ipServingMode;
@@ -1447,13 +1488,18 @@
return;
}
+ // After T, tethering always trust the iface pass by state change intent. This allow
+ // tethering to deprecate tetherable wifi regexs after T.
+ final int type = SdkLevel.isAtLeastT() ? TETHERING_WIFI : ifaceNameToType(ifname);
+ if (!checkTetherableType(type)) {
+ mLog.e(ifname + " is not a tetherable iface, ignoring");
+ return;
+ }
+
if (!TextUtils.isEmpty(ifname)) {
- ensureIpServerStarted(ifname);
- changeInterfaceState(ifname, ipServingMode);
+ enableIpServing(type, ifname, ipServingMode);
} else {
- mLog.e(String.format(
- "Cannot enable IP serving in mode %s on missing interface name",
- ipServingMode));
+ mLog.e("Cannot enable IP serving on missing interface name");
}
}
@@ -2724,23 +2770,28 @@
mTetherMainSM.sendMessage(which, state, 0, newLp);
}
+ private boolean hasSystemFeature(final String feature) {
+ return mContext.getPackageManager().hasSystemFeature(feature);
+ }
+
+ private boolean checkTetherableType(int type) {
+ if ((type == TETHERING_WIFI || type == TETHERING_WIGIG)
+ && !hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ return false;
+ }
+
+ if (type == TETHERING_WIFI_P2P && !hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
+ return false;
+ }
+
+ return type != TETHERING_INVALID;
+ }
+
private void ensureIpServerStarted(final String iface) {
// If we don't care about this type of interface, ignore.
final int interfaceType = ifaceNameToType(iface);
- if (interfaceType == TETHERING_INVALID) {
- mLog.log(iface + " is not a tetherable iface, ignoring");
- return;
- }
-
- final PackageManager pm = mContext.getPackageManager();
- if ((interfaceType == TETHERING_WIFI || interfaceType == TETHERING_WIGIG)
- && !pm.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- mLog.log(iface + " is not tetherable, because WiFi feature is disabled");
- return;
- }
- if (interfaceType == TETHERING_WIFI_P2P
- && !pm.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
- mLog.log(iface + " is not tetherable, because WiFi Direct feature is disabled");
+ if (!checkTetherableType(interfaceType)) {
+ mLog.log(iface + " is used for " + interfaceType + " which is not tetherable");
return;
}
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index a4d0448..31c3df3 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -49,7 +49,7 @@
// Use with NetworkStackJarJarRules.
android_library {
name: "TetheringIntegrationTestsLatestSdkLib",
- target_sdk_version: "31",
+ target_sdk_version: "33",
platform_apis: true,
defaults: ["TetheringIntegrationTestsDefaults"],
visibility: [
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 3699f7a..92be84d 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -27,18 +27,24 @@
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringTester.RemoteResponder;
+import static android.net.TetheringTester.isIcmpv6Type;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_IPV6;
import static android.system.OsConstants.IPPROTO_UDP;
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;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.testutils.DeviceInfoUtils.KVersion;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -71,6 +77,7 @@
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.PacketBuilder;
import com.android.net.module.util.Struct;
import com.android.net.module.util.bpf.Tether4Key;
@@ -99,6 +106,7 @@
import java.io.FileDescriptor;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
@@ -143,6 +151,9 @@
private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
private static final InetAddress TEST_IP6_DNS = parseNumericAddress("2001:db8:1::888");
+ private static final IpPrefix TEST_NAT64PREFIX = new IpPrefix("64:ff9b::/96");
+ private static final Inet6Address REMOTE_NAT64_ADDR =
+ (Inet6Address) parseNumericAddress("64:ff9b::808:808");
private static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
@@ -152,6 +163,10 @@
private static final String BASE64_DELIMITER = ",";
private static final String LINE_DELIMITER = "\\n";
+ // version=6, traffic class=0x0, flowlabel=0x0;
+ private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000;
+ private static final short HOP_LIMIT = 0x40;
+
private final Context mContext = InstrumentationRegistry.getContext();
private final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
private final TetheringManager mTm = mContext.getSystemService(TetheringManager.class);
@@ -779,21 +794,27 @@
}
}
- private TestNetworkTracker createTestUpstream(final List<LinkAddress> addresses)
- throws Exception {
+ private TestNetworkTracker createTestUpstream(final List<LinkAddress> addresses,
+ final List<InetAddress> dnses) throws Exception {
mTm.setPreferTestNetworks(true);
- return initTestNetwork(mContext, addresses, TIMEOUT_MS);
+ final LinkProperties lp = new LinkProperties();
+ lp.setLinkAddresses(addresses);
+ lp.setDnsServers(dnses);
+ lp.setNat64Prefix(TEST_NAT64PREFIX);
+
+ return initTestNetwork(mContext, lp, TIMEOUT_MS);
}
@Test
- public void testTestNetworkUpstream() throws Exception {
+ public void testIcmpv6Echo() throws Exception {
assumeFalse(mEm.isAvailable());
// MyTetheringEventCallback currently only support await first available upstream. Tethering
// may select internet network as upstream if test network is not available and not be
// preferred yet. Create test upstream network before enable tethering.
- mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR, TEST_IP6_ADDR));
+ mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR, TEST_IP6_ADDR),
+ toList(TEST_IP4_DNS, TEST_IP6_DNS));
mDownstreamIface = createTestInterface();
mEm.setIncludeTestInterfaces(true);
@@ -808,7 +829,40 @@
mTetheringEventCallback.awaitUpstreamChanged());
mDownstreamReader = makePacketReader(mDownstreamIface);
- // TODO: do basic forwarding test here.
+ mUpstreamReader = makePacketReader(mUpstreamTracker.getTestIface());
+
+ runPing6Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader));
+ }
+
+ private void runPing6Test(TetheringTester tester, RemoteResponder remote) throws Exception {
+ // Currently tethering don't have API to tell when ipv6 tethering is available. Thus, let
+ // TetheringTester test ipv6 tethering connectivity before testing ipv6.
+ // TODO: move to a common place to avoid that every IPv6 test needs to call this function.
+ tester.waitForIpv6TetherConnectivityVerified();
+
+ TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString("1:2:3:4:5:6"),
+ true /* hasIpv6 */);
+ Inet6Address remoteIp6Addr = (Inet6Address) parseNumericAddress("2400:222:222::222");
+ ByteBuffer request = Ipv6Utils.buildEchoRequestPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv6Addr, remoteIp6Addr);
+ tester.sendPacket(request);
+
+ final byte[] echoRequest = remote.getNextMatchedPacket((p) -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+
+ return isIcmpv6Type(p, false /* hasEth */, ICMPV6_ECHO_REQUEST_TYPE);
+ });
+ assertNotNull("No icmpv6 echo request in upstream", echoRequest);
+
+ ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(remoteIp6Addr, tethered.ipv6Addr);
+ remote.sendPacket(reply);
+
+ final byte[] echoReply = tester.getNextMatchedPacket((p) -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+
+ return isIcmpv6Type(p, true /* hasEth */, ICMPV6_ECHO_REPLY_TYPE);
+ });
+ assertNotNull("No icmpv6 echo reply in downstream", echoReply);
}
// Test network topology:
@@ -826,12 +880,11 @@
// Used by public port and private port. Assume port 9876 has not been used yet before the
// testing that public port and private port are the same in the testing. Note that NAT port
// forwarding could be different between private port and public port.
+ // TODO: move to the start of test class.
private static final short LOCAL_PORT = 9876;
private static final short REMOTE_PORT = 433;
private static final byte TYPE_OF_SERVICE = 0;
private static final short ID = 27149;
- private static final short ID2 = 27150;
- private static final short ID3 = 27151;
private static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0
private static final byte TIME_TO_LIVE = (byte) 0x40;
private static final ByteBuffer PAYLOAD =
@@ -842,19 +895,20 @@
ByteBuffer.wrap(new byte[] { (byte) 0x9a, (byte) 0xbc });
private boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEther,
- @NonNull final ByteBuffer payload) {
+ boolean isIpv4, @NonNull final ByteBuffer payload) {
final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
if (hasEther) {
- final EthernetHeader etherHeader = Struct.parse(EthernetHeader.class, buf);
- if (etherHeader == null) return false;
+ if (Struct.parse(EthernetHeader.class, buf) == null) return false;
}
- final Ipv4Header ipv4Header = Struct.parse(Ipv4Header.class, buf);
- if (ipv4Header == null) return false;
+ if (isIpv4) {
+ if (Struct.parse(Ipv4Header.class, buf) == null) return false;
+ } else {
+ if (Struct.parse(Ipv6Header.class, buf) == null) return false;
+ }
- final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf);
- if (udpHeader == null) return false;
+ if (Struct.parse(UdpHeader.class, buf) == null) return false;
if (buf.remaining() != payload.limit()) return false;
@@ -863,21 +917,47 @@
}
@NonNull
- private ByteBuffer buildUdpv4Packet(@Nullable final MacAddress srcMac,
- @Nullable final MacAddress dstMac, short id,
- @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp,
+ private ByteBuffer buildUdpPacket(
+ @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
+ @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp,
short srcPort, short dstPort, @Nullable final ByteBuffer payload)
throws Exception {
+ int ipProto;
+ short ethType;
+ if (srcIp instanceof Inet4Address && dstIp instanceof Inet4Address) {
+ ipProto = IPPROTO_IP;
+ ethType = (short) ETHER_TYPE_IPV4;
+ } else if (srcIp instanceof Inet6Address && dstIp instanceof Inet6Address) {
+ ipProto = IPPROTO_IPV6;
+ ethType = (short) ETHER_TYPE_IPV6;
+ } else {
+ fail("Unsupported conditions: srcIp " + srcIp + ", dstIp " + dstIp);
+ // Make compiler happy to the uninitialized ipProto and ethType.
+ return null; // unreachable, the annotation @NonNull of function return value is true.
+ }
+
final boolean hasEther = (srcMac != null && dstMac != null);
final int payloadLen = (payload == null) ? 0 : payload.limit();
- final ByteBuffer buffer = PacketBuilder.allocate(hasEther, IPPROTO_IP, IPPROTO_UDP,
+ final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_UDP,
payloadLen);
final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+ // [1] Ethernet header
if (hasEther) packetBuilder.writeL2Header(srcMac, dstMac, (short) ETHER_TYPE_IPV4);
- packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
- TIME_TO_LIVE, (byte) IPPROTO_UDP, srcIp, dstIp);
+
+ // [2] IP header
+ if (ipProto == IPPROTO_IP) {
+ packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+ TIME_TO_LIVE, (byte) IPPROTO_UDP, (Inet4Address) srcIp, (Inet4Address) dstIp);
+ } else {
+ packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_UDP,
+ HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp);
+ }
+
+ // [3] UDP header
packetBuilder.writeUdpHeader(srcPort, dstPort);
+
+ // [4] Payload
if (payload != null) {
buffer.put(payload);
// in case data might be reused by caller, restore the position and
@@ -889,10 +969,10 @@
}
@NonNull
- private ByteBuffer buildUdpv4Packet(short id, @NonNull final Inet4Address srcIp,
- @NonNull final Inet4Address dstIp, short srcPort, short dstPort,
+ private ByteBuffer buildUdpPacket(@NonNull final InetAddress srcIp,
+ @NonNull final InetAddress dstIp, short srcPort, short dstPort,
@Nullable final ByteBuffer payload) throws Exception {
- return buildUdpv4Packet(null /* srcMac */, null /* dstMac */, id, srcIp, dstIp, srcPort,
+ return buildUdpPacket(null /* srcMac */, null /* dstMac */, srcIp, dstIp, srcPort,
dstPort, payload);
}
@@ -900,9 +980,9 @@
// See #runUdp4Test.
private boolean isIpv4TetherConnectivityVerified(TetheringTester tester,
RemoteResponder remote, TetheredDevice tethered) throws Exception {
- final ByteBuffer probePacket = buildUdpv4Packet(tethered.macAddr,
- tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */,
- REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /*dstPort */,
+ final ByteBuffer probePacket = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
TEST_REACHABILITY_PAYLOAD);
// Send a UDP packet from client and check the packet can be found on upstream interface.
@@ -910,7 +990,8 @@
tester.sendPacket(probePacket);
byte[] expectedPacket = remote.getNextMatchedPacket(p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, TEST_REACHABILITY_PAYLOAD);
+ return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */,
+ TEST_REACHABILITY_PAYLOAD);
});
if (expectedPacket != null) return true;
}
@@ -920,7 +1001,7 @@
private void runUdp4Test(TetheringTester tester, RemoteResponder remote, boolean usingBpf)
throws Exception {
final TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString(
- "1:2:3:4:5:6"));
+ "1:2:3:4:5:6"), false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
// Because async upstream connected notification can't guarantee the tethering routing is
@@ -931,23 +1012,23 @@
assertTrue(isIpv4TetherConnectivityVerified(tester, remote, tethered));
// Send a UDP packet in original direction.
- final ByteBuffer originalPacket = buildUdpv4Packet(tethered.macAddr,
- tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */,
- REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /*dstPort */,
+ final ByteBuffer originalPacket = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
PAYLOAD /* payload */);
tester.verifyUpload(remote, originalPacket, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD);
+ return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD);
});
// Send a UDP packet in reply direction.
final Inet4Address publicIp4Addr = (Inet4Address) TEST_IP4_ADDR.getAddress();
- final ByteBuffer replyPacket = buildUdpv4Packet(ID2, REMOTE_IP4_ADDR /* srcIp */,
- publicIp4Addr /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /*dstPort */,
+ final ByteBuffer replyPacket = buildUdpPacket(REMOTE_IP4_ADDR /* srcIp */,
+ publicIp4Addr /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */,
PAYLOAD2 /* payload */);
remote.verifyDownload(tester, replyPacket, p -> {
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, true/* hasEther */, PAYLOAD2);
+ return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2);
});
if (usingBpf) {
@@ -960,13 +1041,13 @@
// See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and
// nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c
Thread.sleep(UDP_STREAM_TS_MS);
- final ByteBuffer originalPacket2 = buildUdpv4Packet(tethered.macAddr,
- tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */,
+ final ByteBuffer originalPacket2 = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */,
- REMOTE_PORT /*dstPort */, PAYLOAD3 /* payload */);
+ REMOTE_PORT /* dstPort */, PAYLOAD3 /* payload */);
tester.verifyUpload(remote, originalPacket2, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD3);
+ return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD3);
});
// [1] Verify IPv4 upstream rule map.
@@ -1002,7 +1083,7 @@
for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
tester.verifyUpload(remote, originalPacket, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD);
+ return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD);
});
}
@@ -1010,7 +1091,7 @@
for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
remote.verifyDownload(tester, replyPacket, p -> {
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, true/* hasEther */, PAYLOAD2);
+ return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2);
});
}
@@ -1035,13 +1116,14 @@
}
}
- void initializeTethering() throws Exception {
+ void initializeTethering(List<LinkAddress> upstreamAddresses, List<InetAddress> upstreamDnses)
+ throws Exception {
assumeFalse(mEm.isAvailable());
// MyTetheringEventCallback currently only support await first available upstream. Tethering
// may select internet network as upstream if test network is not available and not be
// preferred yet. Create test upstream network before enable tethering.
- mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR));
+ mUpstreamTracker = createTestUpstream(upstreamAddresses, upstreamDnses);
mDownstreamIface = createTestInterface();
mEm.setIncludeTestInterfaces(true);
@@ -1062,28 +1144,48 @@
@Test
@IgnoreAfter(Build.VERSION_CODES.R)
public void testTetherUdpV4UpToR() throws Exception {
- initializeTethering();
+ initializeTethering(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS));
runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
false /* usingBpf */);
}
- private static boolean isUdpOffloadSupportedByKernel() {
- final String kVersionString = VintfRuntimeInfo.getKernelVersion();
- // Kernel version which is older than 4.14 doesn't support UDP offload absolutely. Kernel
- // version which is between 4.14 and 5.8 support UDP offload probably. Simply apply kernel
- // 4.14 to be threshold first and monitor on what devices tests fail for improving the
- // offload support checking.
- return DeviceInfoUtils.compareMajorMinorVersion(kVersionString, "4.14") >= 0;
+ private static boolean isUdpOffloadSupportedByKernel(final String kernelVersion) {
+ final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
+ return current.isInRange(new KVersion(4, 14, 222), new KVersion(4, 19, 0))
+ || current.isInRange(new KVersion(4, 19, 176), new KVersion(5, 4, 0))
+ || current.isAtLeast(new KVersion(5, 4, 98));
}
@Test
+ public void testIsUdpOffloadSupportedByKernel() throws Exception {
+ assertFalse(isUdpOffloadSupportedByKernel("4.14.221"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.14.222"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.16.0"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.18.0"));
+ assertFalse(isUdpOffloadSupportedByKernel("4.19.0"));
+
+ assertFalse(isUdpOffloadSupportedByKernel("4.19.175"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.19.176"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.2.0"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.3.0"));
+ assertFalse(isUdpOffloadSupportedByKernel("5.4.0"));
+
+ assertFalse(isUdpOffloadSupportedByKernel("5.4.97"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.4.98"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.10.0"));
+ }
+
+ // TODO: refactor test testTetherUdpV4* into IPv4 UDP non-offload and offload tests.
+ // That can be easier to know which feature is verified from test results.
+ @Test
@IgnoreUpTo(Build.VERSION_CODES.R)
public void testTetherUdpV4AfterR() throws Exception {
- initializeTethering();
- boolean usingBpf = isUdpOffloadSupportedByKernel();
+ initializeTethering(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS));
+ final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
+ boolean usingBpf = isUdpOffloadSupportedByKernel(kernelVersion);
if (!usingBpf) {
Log.i(TAG, "testTetherUdpV4AfterR will skip BPF offload test for kernel "
- + VintfRuntimeInfo.getKernelVersion());
+ + kernelVersion);
}
runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
usingBpf);
@@ -1145,6 +1247,96 @@
return null;
}
+ @Nullable
+ private Inet6Address getClatIpv6Address(TetheringTester tester,
+ RemoteResponder remote, TetheredDevice tethered) throws Exception {
+ final ByteBuffer probePacket = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
+ TEST_REACHABILITY_PAYLOAD);
+
+ // Send an IPv4 UDP packet from client and check that a CLAT translated IPv6 UDP packet can
+ // be found on upstream interface. Get CLAT IPv6 address from the CLAT translated IPv6 UDP
+ // packet.
+ byte[] expectedPacket = null;
+ for (int i = 0; i < TETHER_REACHABILITY_ATTEMPTS; i++) {
+ tester.sendPacket(probePacket);
+ expectedPacket = remote.getNextMatchedPacket(p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, false /* hasEther */, false /* isIpv4 */,
+ TEST_REACHABILITY_PAYLOAD);
+ });
+ if (expectedPacket != null) break;
+ }
+ if (expectedPacket == null) return null;
+
+ // Above has guaranteed that the found packet is an IPv6 packet without ether header.
+ final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class,
+ ByteBuffer.wrap(expectedPacket));
+ return ipv6Header.srcIp;
+ }
+
+ // Test network topology:
+ //
+ // public network (rawip) private network
+ // | UE (CLAT support) |
+ // +---------------+ V +------------+------------+ V +------------+
+ // | NAT64 Gateway +---------+ Upstream | Downstream +---------+ Client |
+ // +---------------+ +------------+------------+ +------------+
+ // remote ip public ip private ip
+ // [64:ff9b::808:808]:443 [clat ipv6]:9876 [TetheredDevice ipv4]:9876
+ //
+ // Note that CLAT IPv6 address is generated by ClatCoordinator. Get the CLAT IPv6 address by
+ // sending out an IPv4 packet and extracting the source address from CLAT translated IPv6
+ // packet.
+ //
+ private void runClatUdpTest(TetheringTester tester, RemoteResponder remote)
+ throws Exception {
+ // Currently tethering don't have API to tell when ipv6 tethering is available. Thus, let
+ // TetheringTester test ipv6 tethering connectivity before testing ipv6.
+ // TODO: move to a common place to avoid that every IPv6 test needs to call this function.
+ tester.waitForIpv6TetherConnectivityVerified();
+
+ final TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString(
+ "1:2:3:4:5:6"), true /* hasIpv6 */);
+
+ // Get CLAT IPv6 address.
+ final Inet6Address clatAddr6 = getClatIpv6Address(tester, remote, tethered);
+ assertNotNull(clatAddr6);
+
+ // Send an IPv4 UDP packet in original direction.
+ // IPv4 packet -- CLAT translation --> IPv6 packet
+ final ByteBuffer originalPacket = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
+ PAYLOAD /* payload */);
+ tester.verifyUpload(remote, originalPacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, false /* hasEther */, false /* isIpv4 */, PAYLOAD);
+ });
+
+ // Send an IPv6 UDP packet in reply direction.
+ // IPv6 packet -- CLAT translation --> IPv4 packet
+ final ByteBuffer replyPacket = buildUdpPacket(REMOTE_NAT64_ADDR /* srcIp */,
+ clatAddr6 /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */,
+ PAYLOAD2 /* payload */);
+ remote.verifyDownload(tester, replyPacket, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2);
+ });
+
+ // TODO: test CLAT bpf maps.
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testTetherClatUdp() throws Exception {
+ // CLAT only starts on IPv6 only network.
+ initializeTethering(toList(TEST_IP6_ADDR), toList(TEST_IP6_DNS));
+ runClatUdpTest(new TetheringTester(mDownstreamReader),
+ new RemoteResponder(mUpstreamReader));
+ }
+
private <T> List<T> toList(T... array) {
return Arrays.asList(array);
}
diff --git a/Tethering/tests/integration/src/android/net/TetheringTester.java b/Tethering/tests/integration/src/android/net/TetheringTester.java
index d24661a..458680a 100644
--- a/Tethering/tests/integration/src/android/net/TetheringTester.java
+++ b/Tethering/tests/integration/src/android/net/TetheringTester.java
@@ -16,10 +16,22 @@
package android.net;
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+
import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -32,11 +44,24 @@
import androidx.annotation.Nullable;
+import com.android.net.module.util.Ipv6Utils;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.LlaOption;
+import com.android.net.module.util.structs.NsHeader;
+import com.android.net.module.util.structs.PrefixInformationOption;
+import com.android.net.module.util.structs.RaHeader;
import com.android.networkstack.arp.ArpPacket;
import com.android.testutils.TapPacketReader;
import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
@@ -50,12 +75,14 @@
private static final String TAG = TetheringTester.class.getSimpleName();
private static final int PACKET_READ_TIMEOUT_MS = 100;
private static final int DHCP_DISCOVER_ATTEMPTS = 10;
+ private static final int READ_RA_ATTEMPTS = 10;
private static final byte[] DHCP_REQUESTED_PARAMS = new byte[] {
DhcpPacket.DHCP_SUBNET_MASK,
DhcpPacket.DHCP_ROUTER,
DhcpPacket.DHCP_DNS_SERVER,
DhcpPacket.DHCP_LEASE_TIME,
};
+ private static final InetAddress LINK_LOCAL = parseNumericAddress("fe80::1");
public static final String DHCP_HOSTNAME = "testhostname";
@@ -69,12 +96,13 @@
mTetheredDevices = new ArrayMap<>();
}
- public TetheredDevice createTetheredDevice(MacAddress macAddr) throws Exception {
+ public TetheredDevice createTetheredDevice(MacAddress macAddr, boolean hasIpv6)
+ throws Exception {
if (mTetheredDevices.get(macAddr) != null) {
fail("Tethered device already created");
}
- TetheredDevice tethered = new TetheredDevice(macAddr);
+ TetheredDevice tethered = new TetheredDevice(macAddr, hasIpv6);
mTetheredDevices.put(macAddr, tethered);
return tethered;
@@ -84,14 +112,15 @@
public final MacAddress macAddr;
public final MacAddress routerMacAddr;
public final Inet4Address ipv4Addr;
+ public final Inet6Address ipv6Addr;
- private TetheredDevice(MacAddress mac) throws Exception {
+ private TetheredDevice(MacAddress mac, boolean hasIpv6) throws Exception {
macAddr = mac;
-
DhcpResults dhcpResults = runDhcp(macAddr.toByteArray());
ipv4Addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
routerMacAddr = getRouterMacAddressFromArp(ipv4Addr, macAddr,
dhcpResults.serverAddress);
+ ipv6Addr = hasIpv6 ? runSlaac(macAddr, routerMacAddr) : null;
}
}
@@ -216,6 +245,141 @@
return null;
}
+ public void waitForIpv6TetherConnectivityVerified() throws Exception {
+ Log.d(TAG, "Waiting RA multicast");
+
+ // Wait for RA multicast message from router to confirm that the IPv6 tethering
+ // connectivity is ready. We don't extract the router mac address from RA because
+ // we get the router mac address from IPv4 ARP packet. See #getRouterMacAddressFromArp.
+ for (int i = 0; i < READ_RA_ATTEMPTS; i++) {
+ final byte[] raPacket = getNextMatchedPacket((p) -> {
+ return isIcmpv6Type(p, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT);
+ });
+ if (raPacket != null) return;
+ }
+
+ fail("Could not get RA multicast packet after " + READ_RA_ATTEMPTS + " attempts");
+ }
+
+ private List<PrefixInformationOption> getRaPrefixOptions(byte[] packet) {
+ ByteBuffer buf = ByteBuffer.wrap(packet);
+ if (!isIcmpv6Type(buf, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) return null;
+
+ Struct.parse(RaHeader.class, buf);
+ final ArrayList<PrefixInformationOption> pioList = new ArrayList<>();
+ while (buf.position() < packet.length) {
+ final int currentPos = buf.position();
+ final int type = Byte.toUnsignedInt(buf.get());
+ final int length = Byte.toUnsignedInt(buf.get());
+ if (type == ICMPV6_ND_OPTION_PIO) {
+ final ByteBuffer pioBuf = ByteBuffer.wrap(buf.array(), currentPos,
+ Struct.getSize(PrefixInformationOption.class));
+ final PrefixInformationOption pio =
+ Struct.parse(PrefixInformationOption.class, pioBuf);
+ pioList.add(pio);
+
+ // Move ByteBuffer position to the next option.
+ buf.position(currentPos + Struct.getSize(PrefixInformationOption.class));
+ } else {
+ buf.position(currentPos + (length * 8));
+ }
+ }
+ return pioList;
+ }
+
+ private Inet6Address runSlaac(MacAddress srcMac, MacAddress dstMac) throws Exception {
+ sendRsPacket(srcMac, dstMac);
+
+ final byte[] raPacket = getNextMatchedPacket((p) -> {
+ return isIcmpv6Type(p, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT);
+ });
+
+ if (raPacket == null) {
+ fail("Could not get ra for prefix options");
+ }
+ final List<PrefixInformationOption> options = getRaPrefixOptions(raPacket);
+
+ for (PrefixInformationOption pio : options) {
+ if (pio.validLifetime > 0) {
+ final byte[] addressBytes = pio.prefix;
+ // Random the last two bytes as suffix.
+ // TODO: Currently do not implmement DAD in the test. Rely the gateway ipv6 address
+ // genetrated by tethering module always has random the last byte.
+ addressBytes[addressBytes.length - 1] = (byte) (new Random()).nextInt();
+ addressBytes[addressBytes.length - 2] = (byte) (new Random()).nextInt();
+
+ return (Inet6Address) InetAddress.getByAddress(addressBytes);
+ }
+ }
+
+ fail("Could not get ipv6 address");
+ return null;
+ }
+
+ private void sendRsPacket(MacAddress srcMac, MacAddress dstMac) throws Exception {
+ Log.d(TAG, "Sending RS");
+ ByteBuffer slla = LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac);
+ ByteBuffer rs = Ipv6Utils.buildRsPacket(srcMac, dstMac, (Inet6Address) LINK_LOCAL,
+ IPV6_ADDR_ALL_NODES_MULTICAST, slla);
+
+ sendPacket(rs);
+ }
+
+ private void maybeReplyNa(byte[] packet) {
+ ByteBuffer buf = ByteBuffer.wrap(packet);
+ final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
+ if (ethHdr.etherType != ETHER_TYPE_IPV6) return;
+
+ final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
+ if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return;
+
+ final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
+ if (icmpv6Hdr.type != (short) ICMPV6_NEIGHBOR_SOLICITATION) return;
+
+ final NsHeader nsHdr = Struct.parse(NsHeader.class, buf);
+ for (int i = 0; i < mTetheredDevices.size(); i++) {
+ TetheredDevice tethered = mTetheredDevices.valueAt(i);
+ if (!nsHdr.target.equals(tethered.ipv6Addr)) continue;
+
+ final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, tethered.macAddr);
+ int flags = NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED
+ | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+ ByteBuffer ns = Ipv6Utils.buildNaPacket(tethered.macAddr, tethered.routerMacAddr,
+ nsHdr.target, ipv6Hdr.srcIp, flags, nsHdr.target, tlla);
+ try {
+ sendPacket(ns);
+ } catch (Exception e) {
+ fail("Failed to reply NA for " + tethered.ipv6Addr);
+ }
+
+ return;
+ }
+ }
+
+ public static boolean isIcmpv6Type(byte[] packet, boolean hasEth, int type) {
+ final ByteBuffer buf = ByteBuffer.wrap(packet);
+ return isIcmpv6Type(buf, hasEth, type);
+ }
+
+ private static boolean isIcmpv6Type(ByteBuffer buf, boolean hasEth, int type) {
+ try {
+ if (hasEth) {
+ final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
+ if (ethHdr.etherType != ETHER_TYPE_IPV6) return false;
+ }
+
+ final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
+ if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return false;
+
+ final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
+ return icmpv6Hdr.type == (short) type;
+ } catch (Exception e) {
+ // Parsing packet fail means it is not icmpv6 packet.
+ }
+
+ return false;
+ }
+
public void sendPacket(ByteBuffer packet) throws Exception {
mDownstreamReader.sendResponse(packet);
}
@@ -226,6 +390,7 @@
if (filter.test(packet)) return packet;
maybeReplyArp(packet);
+ maybeReplyNa(packet);
}
return null;
diff --git a/Tethering/tests/mts/Android.bp b/Tethering/tests/mts/Android.bp
index 18fd63b..a84fdd2 100644
--- a/Tethering/tests/mts/Android.bp
+++ b/Tethering/tests/mts/Android.bp
@@ -22,7 +22,7 @@
name: "MtsTetheringTestLatestSdk",
min_sdk_version: "30",
- target_sdk_version: "31",
+ target_sdk_version: "33",
libs: [
"android.test.base",
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index 75c2ad1..68c1c57 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -352,15 +352,6 @@
assertFalse(mTestMap.isEmpty());
mTestMap.clear();
assertTrue(mTestMap.isEmpty());
-
- // Clearing an already-closed map throws.
- mTestMap.close();
- try {
- mTestMap.clear();
- fail("clearing already-closed map should throw");
- } catch (IllegalStateException expected) {
- // ParcelFileDescriptor.getFd throws IllegalStateException: Already closed.
- }
}
@Test
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index d1b8380..0ee12ad 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -89,7 +89,7 @@
static_libs: [
"TetheringApiStableLib",
],
- target_sdk_version: "31",
+ target_sdk_version: "33",
visibility: [
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/Connectivity/Tethering/tests:__subpackages__",
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 e9716b3..8ef0c76 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -26,6 +26,7 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_IFACE;
import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_UID;
@@ -668,7 +669,7 @@
if (isAtLeastT()) {
mTetherStatsProviderCb.expectNotifyLimitReached();
- } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.S) {
+ } else if (isAtLeastS()) {
mTetherStatsProviderCb.expectNotifyWarningOrLimitReached();
} else {
mTetherStatsProviderCb.expectNotifyLimitReached();
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 4662c96..773cae3 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -58,6 +58,7 @@
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -194,11 +195,14 @@
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
import com.android.networkstack.tethering.metrics.TetheringMetrics;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.MiscAsserts;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -221,6 +225,8 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TetheringTest {
+ @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
private static final int IFINDEX_OFFSET = 100;
private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0";
@@ -300,6 +306,7 @@
private OffloadController mOffloadCtrl;
private PrivateAddressCoordinator mPrivateAddressCoordinator;
private SoftApCallback mSoftApCallback;
+ private SoftApCallback mLocalOnlyHotspotCallback;
private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
@@ -670,6 +677,14 @@
verify(mWifiManager).registerSoftApCallback(any(), softApCallbackCaptor.capture());
mSoftApCallback = softApCallbackCaptor.getValue();
+ if (isAtLeastT()) {
+ final ArgumentCaptor<SoftApCallback> localOnlyCallbackCaptor =
+ ArgumentCaptor.forClass(SoftApCallback.class);
+ verify(mWifiManager).registerLocalOnlyHotspotSoftApCallback(any(),
+ localOnlyCallbackCaptor.capture());
+ mLocalOnlyHotspotCallback = localOnlyCallbackCaptor.getValue();
+ }
+
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true);
}
@@ -946,7 +961,7 @@
// Emulate externally-visible WifiManager effects, when hotspot mode
// is being torn down.
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY);
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
@@ -1521,7 +1536,7 @@
// Emulate externally-visible WifiManager effects, when tethering mode
// is being torn down.
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
@@ -1922,7 +1937,13 @@
mTethering.unregisterTetheringEventCallback(callback);
mLooper.dispatchAll();
mTethering.stopTethering(TETHERING_WIFI);
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED);
+ if (isAtLeastT()) {
+ // After T, tethering doesn't support WIFI_AP_STATE_DISABLED with null interface name.
+ callback2.assertNoStateChangeCallback();
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME,
+ IFACE_IP_MODE_TETHERED);
+ }
tetherState = callback2.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
mLooper.dispatchAll();
@@ -2530,12 +2551,11 @@
eventCallbacks = dhcpEventCbsCaptor.getValue();
// Update lease for local only tethering.
final MacAddress testMac1 = MacAddress.fromString("11:11:11:11:11:11");
- final ArrayList<DhcpLeaseParcelable> p2pLeases = new ArrayList<>();
- p2pLeases.add(createDhcpLeaseParcelable("clientId1", testMac1, "192.168.50.24", 24,
- Long.MAX_VALUE, "test1"));
- notifyDhcpLeasesChanged(p2pLeases, eventCallbacks);
- final List<TetheredClient> clients = toTetheredClients(p2pLeases, TETHERING_WIFI_P2P);
- callback.expectTetheredClientChanged(clients);
+ final DhcpLeaseParcelable p2pLease = createDhcpLeaseParcelable("clientId1", testMac1,
+ "192.168.50.24", 24, Long.MAX_VALUE, "test1");
+ final List<TetheredClient> p2pClients = notifyDhcpLeasesChanged(TETHERING_WIFI_P2P,
+ eventCallbacks, p2pLease);
+ callback.expectTetheredClientChanged(p2pClients);
reset(mDhcpServer);
// Run wifi tethering.
@@ -2545,25 +2565,20 @@
any(), dhcpEventCbsCaptor.capture());
eventCallbacks = dhcpEventCbsCaptor.getValue();
// Update mac address from softAp callback before getting dhcp lease.
- final ArrayList<WifiClient> wifiClients = new ArrayList<>();
final MacAddress testMac2 = MacAddress.fromString("22:22:22:22:22:22");
- final WifiClient testClient = mock(WifiClient.class);
- when(testClient.getMacAddress()).thenReturn(testMac2);
- wifiClients.add(testClient);
- mSoftApCallback.onConnectedClientsChanged(wifiClients);
- final TetheredClient noAddrClient = new TetheredClient(testMac2,
- Collections.emptyList() /* addresses */, TETHERING_WIFI);
- clients.add(noAddrClient);
- callback.expectTetheredClientChanged(clients);
+ final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac2,
+ false /* isLocalOnly */);
+ final List<TetheredClient> p2pAndNoAddrClients = new ArrayList<>(p2pClients);
+ p2pAndNoAddrClients.add(noAddrClient);
+ callback.expectTetheredClientChanged(p2pAndNoAddrClients);
// Update dhcp lease for wifi tethering.
- clients.remove(noAddrClient);
- final ArrayList<DhcpLeaseParcelable> wifiLeases = new ArrayList<>();
- wifiLeases.add(createDhcpLeaseParcelable("clientId2", testMac2, "192.168.43.24", 24,
- Long.MAX_VALUE, "test2"));
- notifyDhcpLeasesChanged(wifiLeases, eventCallbacks);
- clients.addAll(toTetheredClients(wifiLeases, TETHERING_WIFI));
- callback.expectTetheredClientChanged(clients);
+ final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId2", testMac2,
+ "192.168.43.24", 24, Long.MAX_VALUE, "test2");
+ final List<TetheredClient> p2pAndWifiClients = new ArrayList<>(p2pClients);
+ p2pAndWifiClients.addAll(notifyDhcpLeasesChanged(TETHERING_WIFI,
+ eventCallbacks, wifiLease));
+ callback.expectTetheredClientChanged(p2pAndWifiClients);
// Test onStarted callback that register second callback when tethering is running.
TestTetheringEventCallback callback2 = new TestTetheringEventCallback();
@@ -2571,18 +2586,74 @@
mTethering.registerTetheringEventCallback(callback2);
mLooper.dispatchAll();
});
- callback2.expectTetheredClientChanged(clients);
+ callback2.expectTetheredClientChanged(p2pAndWifiClients);
}
- private void notifyDhcpLeasesChanged(List<DhcpLeaseParcelable> leaseParcelables,
- IDhcpEventCallbacks callback) throws Exception {
- callback.onLeasesChanged(leaseParcelables);
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testUpdateConnectedClientsForLocalOnlyHotspot() throws Exception {
+ TestTetheringEventCallback callback = new TestTetheringEventCallback();
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mTethering.registerTetheringEventCallback(callback);
+ mLooper.dispatchAll();
+ });
+ callback.expectTetheredClientChanged(Collections.emptyList());
+
+ // Run local only hotspot.
+ mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY);
+
+ final ArgumentCaptor<IDhcpEventCallbacks> dhcpEventCbsCaptor =
+ ArgumentCaptor.forClass(IDhcpEventCallbacks.class);
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
+ any(), dhcpEventCbsCaptor.capture());
+ final IDhcpEventCallbacks eventCallbacks = dhcpEventCbsCaptor.getValue();
+ // Update mac address from softAp callback before getting dhcp lease.
+ final MacAddress testMac = MacAddress.fromString("22:22:22:22:22:22");
+ final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac,
+ true /* isLocalOnly */);
+ final List<TetheredClient> noAddrLocalOnlyClients = new ArrayList<>();
+ noAddrLocalOnlyClients.add(noAddrClient);
+ callback.expectTetheredClientChanged(noAddrLocalOnlyClients);
+
+ // Update dhcp lease for local only hotspot.
+ final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId", testMac,
+ "192.168.43.24", 24, Long.MAX_VALUE, "test");
+ final List<TetheredClient> localOnlyClients = notifyDhcpLeasesChanged(TETHERING_WIFI,
+ eventCallbacks, wifiLease);
+ callback.expectTetheredClientChanged(localOnlyClients);
+
+ // Client disconnect from local only hotspot.
+ mLocalOnlyHotspotCallback.onConnectedClientsChanged(Collections.emptyList());
+ callback.expectTetheredClientChanged(Collections.emptyList());
+ }
+
+ private TetheredClient notifyConnectedWifiClientsChanged(final MacAddress mac,
+ boolean isLocalOnly) throws Exception {
+ final ArrayList<WifiClient> wifiClients = new ArrayList<>();
+ final WifiClient testClient = mock(WifiClient.class);
+ when(testClient.getMacAddress()).thenReturn(mac);
+ wifiClients.add(testClient);
+ if (isLocalOnly) {
+ mLocalOnlyHotspotCallback.onConnectedClientsChanged(wifiClients);
+ } else {
+ mSoftApCallback.onConnectedClientsChanged(wifiClients);
+ }
+ return new TetheredClient(mac, Collections.emptyList() /* addresses */, TETHERING_WIFI);
+ }
+
+ private List<TetheredClient> notifyDhcpLeasesChanged(int type, IDhcpEventCallbacks callback,
+ DhcpLeaseParcelable... leases) throws Exception {
+ final List<DhcpLeaseParcelable> dhcpLeases = Arrays.asList(leases);
+ callback.onLeasesChanged(dhcpLeases);
mLooper.dispatchAll();
+
+ return toTetheredClients(dhcpLeases, type);
}
private List<TetheredClient> toTetheredClients(List<DhcpLeaseParcelable> leaseParcelables,
int type) throws Exception {
- final ArrayList<TetheredClient> leases = new ArrayList<>();
+ final ArrayList<TetheredClient> clients = new ArrayList<>();
for (DhcpLeaseParcelable lease : leaseParcelables) {
final LinkAddress address = new LinkAddress(
intToInet4AddressHTH(lease.netAddr), lease.prefixLength,
@@ -2592,13 +2663,13 @@
final MacAddress macAddress = MacAddress.fromBytes(lease.hwAddr);
final AddressInfo addressInfo = new TetheredClient.AddressInfo(address, lease.hostname);
- leases.add(new TetheredClient(
+ clients.add(new TetheredClient(
macAddress,
Collections.singletonList(addressInfo),
type));
}
- return leases;
+ return clients;
}
private DhcpLeaseParcelable createDhcpLeaseParcelable(final String clientId,
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 6c78244..23af3e3 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -123,5 +123,5 @@
include_dirs: [
"frameworks/libs/net/common/netd/libnetdutils/include",
],
- sub_dir: "net_shared",
+ sub_dir: "netd_shared",
}
diff --git a/bpf_progs/block.c b/bpf_progs/block.c
index ddd9a1c..f2a3e62 100644
--- a/bpf_progs/block.c
+++ b/bpf_progs/block.c
@@ -19,6 +19,9 @@
#include <netinet/in.h>
#include <stdint.h>
+// The resulting .o needs to load on the Android T beta 3 bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+
#include "bpf_helpers.h"
#define ALLOW 1
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/bpf_shared.h
index 9a246a6..706dd1d 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/bpf_shared.h
@@ -98,29 +98,29 @@
static const int CONFIGURATION_MAP_SIZE = 2;
static const int UID_OWNER_MAP_SIZE = 2000;
-#define BPF_PATH "/sys/fs/bpf/net_shared/"
+#define BPF_NETD_PATH "/sys/fs/bpf/netd_shared/"
-#define BPF_EGRESS_PROG_PATH BPF_PATH "prog_netd_cgroupskb_egress_stats"
-#define BPF_INGRESS_PROG_PATH BPF_PATH "prog_netd_cgroupskb_ingress_stats"
-#define XT_BPF_INGRESS_PROG_PATH BPF_PATH "prog_netd_skfilter_ingress_xtbpf"
-#define XT_BPF_EGRESS_PROG_PATH BPF_PATH "prog_netd_skfilter_egress_xtbpf"
-#define XT_BPF_ALLOWLIST_PROG_PATH BPF_PATH "prog_netd_skfilter_allowlist_xtbpf"
-#define XT_BPF_DENYLIST_PROG_PATH BPF_PATH "prog_netd_skfilter_denylist_xtbpf"
-#define CGROUP_SOCKET_PROG_PATH BPF_PATH "prog_netd_cgroupsock_inet_create"
+#define BPF_EGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupskb_egress_stats"
+#define BPF_INGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupskb_ingress_stats"
+#define XT_BPF_INGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_ingress_xtbpf"
+#define XT_BPF_EGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_egress_xtbpf"
+#define XT_BPF_ALLOWLIST_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_allowlist_xtbpf"
+#define XT_BPF_DENYLIST_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_denylist_xtbpf"
+#define CGROUP_SOCKET_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsock_inet_create"
#define TC_BPF_INGRESS_ACCOUNT_PROG_NAME "prog_netd_schedact_ingress_account"
-#define TC_BPF_INGRESS_ACCOUNT_PROG_PATH BPF_PATH TC_BPF_INGRESS_ACCOUNT_PROG_NAME
+#define TC_BPF_INGRESS_ACCOUNT_PROG_PATH BPF_NETD_PATH TC_BPF_INGRESS_ACCOUNT_PROG_NAME
-#define COOKIE_TAG_MAP_PATH BPF_PATH "map_netd_cookie_tag_map"
-#define UID_COUNTERSET_MAP_PATH BPF_PATH "map_netd_uid_counterset_map"
-#define APP_UID_STATS_MAP_PATH BPF_PATH "map_netd_app_uid_stats_map"
-#define STATS_MAP_A_PATH BPF_PATH "map_netd_stats_map_A"
-#define STATS_MAP_B_PATH BPF_PATH "map_netd_stats_map_B"
-#define IFACE_INDEX_NAME_MAP_PATH BPF_PATH "map_netd_iface_index_name_map"
-#define IFACE_STATS_MAP_PATH BPF_PATH "map_netd_iface_stats_map"
-#define CONFIGURATION_MAP_PATH BPF_PATH "map_netd_configuration_map"
-#define UID_OWNER_MAP_PATH BPF_PATH "map_netd_uid_owner_map"
-#define UID_PERMISSION_MAP_PATH BPF_PATH "map_netd_uid_permission_map"
+#define COOKIE_TAG_MAP_PATH BPF_NETD_PATH "map_netd_cookie_tag_map"
+#define UID_COUNTERSET_MAP_PATH BPF_NETD_PATH "map_netd_uid_counterset_map"
+#define APP_UID_STATS_MAP_PATH BPF_NETD_PATH "map_netd_app_uid_stats_map"
+#define STATS_MAP_A_PATH BPF_NETD_PATH "map_netd_stats_map_A"
+#define STATS_MAP_B_PATH BPF_NETD_PATH "map_netd_stats_map_B"
+#define IFACE_INDEX_NAME_MAP_PATH BPF_NETD_PATH "map_netd_iface_index_name_map"
+#define IFACE_STATS_MAP_PATH BPF_NETD_PATH "map_netd_iface_stats_map"
+#define CONFIGURATION_MAP_PATH BPF_NETD_PATH "map_netd_configuration_map"
+#define UID_OWNER_MAP_PATH BPF_NETD_PATH "map_netd_uid_owner_map"
+#define UID_PERMISSION_MAP_PATH BPF_NETD_PATH "map_netd_uid_permission_map"
enum UidOwnerMatchType {
NO_MATCH = 0,
@@ -133,6 +133,9 @@
LOW_POWER_STANDBY_MATCH = (1 << 6),
IIF_MATCH = (1 << 7),
LOCKDOWN_VPN_MATCH = (1 << 8),
+ OEM_DENY_1_MATCH = (1 << 9),
+ OEM_DENY_2_MATCH = (1 << 10),
+ OEM_DENY_3_MATCH = (1 << 11),
};
enum BpfPermissionMatch {
@@ -147,9 +150,9 @@
SELECT_MAP_B,
};
-// TODO: change the configuration object from an 8-bit bitmask to an object with clearer
+// TODO: change the configuration object from a bitmask to an object with clearer
// semantics, like a struct.
-typedef uint8_t BpfConfig;
+typedef uint32_t BpfConfig;
static const BpfConfig DEFAULT_CONFIG = 0;
typedef struct {
@@ -160,16 +163,20 @@
} UidOwnerValue;
STRUCT_SIZE(UidOwnerValue, 2 * 4); // 8
+// Entry in the configuration map that stores which UID rules are enabled.
#define UID_RULES_CONFIGURATION_KEY 1
+// Entry in the configuration map that stores which stats map is currently in use.
#define CURRENT_STATS_MAP_CONFIGURATION_KEY 2
+#define BPF_CLATD_PATH "/sys/fs/bpf/net_shared/"
+
#define CLAT_INGRESS6_PROG_RAWIP_NAME "prog_clatd_schedcls_ingress6_clat_rawip"
#define CLAT_INGRESS6_PROG_ETHER_NAME "prog_clatd_schedcls_ingress6_clat_ether"
-#define CLAT_INGRESS6_PROG_RAWIP_PATH BPF_PATH CLAT_INGRESS6_PROG_RAWIP_NAME
-#define CLAT_INGRESS6_PROG_ETHER_PATH BPF_PATH CLAT_INGRESS6_PROG_ETHER_NAME
+#define CLAT_INGRESS6_PROG_RAWIP_PATH BPF_CLATD_PATH CLAT_INGRESS6_PROG_RAWIP_NAME
+#define CLAT_INGRESS6_PROG_ETHER_PATH BPF_CLATD_PATH CLAT_INGRESS6_PROG_ETHER_NAME
-#define CLAT_INGRESS6_MAP_PATH BPF_PATH "map_clatd_clat_ingress6_map"
+#define CLAT_INGRESS6_MAP_PATH BPF_CLATD_PATH "map_clatd_clat_ingress6_map"
typedef struct {
uint32_t iif; // The input interface index
@@ -187,10 +194,10 @@
#define CLAT_EGRESS4_PROG_RAWIP_NAME "prog_clatd_schedcls_egress4_clat_rawip"
#define CLAT_EGRESS4_PROG_ETHER_NAME "prog_clatd_schedcls_egress4_clat_ether"
-#define CLAT_EGRESS4_PROG_RAWIP_PATH BPF_PATH CLAT_EGRESS4_PROG_RAWIP_NAME
-#define CLAT_EGRESS4_PROG_ETHER_PATH BPF_PATH CLAT_EGRESS4_PROG_ETHER_NAME
+#define CLAT_EGRESS4_PROG_RAWIP_PATH BPF_CLATD_PATH CLAT_EGRESS4_PROG_RAWIP_NAME
+#define CLAT_EGRESS4_PROG_ETHER_PATH BPF_CLATD_PATH CLAT_EGRESS4_PROG_ETHER_NAME
-#define CLAT_EGRESS4_MAP_PATH BPF_PATH "map_clatd_clat_egress4_map"
+#define CLAT_EGRESS4_MAP_PATH BPF_CLATD_PATH "map_clatd_clat_egress4_map"
typedef struct {
uint32_t iif; // The input interface index
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index 9a9d337..c5b8555 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -30,6 +30,9 @@
#define __kernel_udphdr udphdr
#include <linux/udp.h>
+// The resulting .o needs to load on the Android T beta 3 bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
#include "bpf_shared.h"
diff --git a/bpf_progs/dscp_policy.c b/bpf_progs/dscp_policy.c
index d5df7ef..538a9e4 100644
--- a/bpf_progs/dscp_policy.c
+++ b/bpf_progs/dscp_policy.c
@@ -27,6 +27,9 @@
#include <netinet/udp.h>
#include <string.h>
+// The resulting .o needs to load on the Android T beta 3 bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+
#include "bpf_helpers.h"
#include "dscp_policy.h"
diff --git a/bpf_progs/dscp_policy.h b/bpf_progs/dscp_policy.h
index 777c4ff..1637f7a 100644
--- a/bpf_progs/dscp_policy.h
+++ b/bpf_progs/dscp_policy.h
@@ -26,12 +26,11 @@
#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
-#ifndef v6_equal
-#define v6_equal(a, b) (a.s6_addr32[0] == b.s6_addr32[0] && \
- a.s6_addr32[1] == b.s6_addr32[1] && \
- a.s6_addr32[2] == b.s6_addr32[2] && \
- a.s6_addr32[3] == b.s6_addr32[3])
-#endif
+#define v6_equal(a, b) \
+ (((a.s6_addr32[0] ^ b.s6_addr32[0]) | \
+ (a.s6_addr32[1] ^ b.s6_addr32[1]) | \
+ (a.s6_addr32[2] ^ b.s6_addr32[2]) | \
+ (a.s6_addr32[3] ^ b.s6_addr32[3])) == 0)
// TODO: these are already defined in packages/modules/Connectivity/bpf_progs/bpf_net_helpers.h.
// smove to common location in future.
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index d629b41..94d5ed8 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+// The resulting .o needs to load on the Android T Beta 3 bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+
#include <bpf_helpers.h>
#include <linux/bpf.h>
#include <linux/if.h>
@@ -59,7 +62,7 @@
DEFINE_BPF_MAP_GRW(stats_map_B, HASH, StatsKey, StatsValue, STATS_MAP_SIZE, AID_NET_BW_ACCT)
DEFINE_BPF_MAP_GRW(iface_stats_map, HASH, uint32_t, StatsValue, IFACE_STATS_MAP_SIZE,
AID_NET_BW_ACCT)
-DEFINE_BPF_MAP_GRW(configuration_map, HASH, uint32_t, uint8_t, CONFIGURATION_MAP_SIZE,
+DEFINE_BPF_MAP_GRW(configuration_map, HASH, uint32_t, uint32_t, CONFIGURATION_MAP_SIZE,
AID_NET_BW_ACCT)
DEFINE_BPF_MAP_GRW(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE,
AID_NET_BW_ACCT)
@@ -213,6 +216,15 @@
if ((enabledRules & LOW_POWER_STANDBY_MATCH) && !(uidRules & LOW_POWER_STANDBY_MATCH)) {
return BPF_DROP;
}
+ if ((enabledRules & OEM_DENY_1_MATCH) && (uidRules & OEM_DENY_1_MATCH)) {
+ return BPF_DROP;
+ }
+ if ((enabledRules & OEM_DENY_2_MATCH) && (uidRules & OEM_DENY_2_MATCH)) {
+ return BPF_DROP;
+ }
+ if ((enabledRules & OEM_DENY_3_MATCH) && (uidRules & OEM_DENY_3_MATCH)) {
+ return BPF_DROP;
+ }
}
if (direction == BPF_INGRESS && skb->ifindex != 1) {
if (uidRules & IIF_MATCH) {
@@ -231,7 +243,7 @@
}
static __always_inline inline void update_stats_with_config(struct __sk_buff* skb, int direction,
- StatsKey* key, uint8_t selectedMap) {
+ StatsKey* key, uint32_t selectedMap) {
if (selectedMap == SELECT_MAP_A) {
update_stats_map_A(skb, direction, key);
} else if (selectedMap == SELECT_MAP_B) {
@@ -283,7 +295,7 @@
if (counterSet) key.counterSet = (uint32_t)*counterSet;
uint32_t mapSettingKey = CURRENT_STATS_MAP_CONFIGURATION_KEY;
- uint8_t* selectedMap = bpf_configuration_map_lookup_elem(&mapSettingKey);
+ uint32_t* selectedMap = bpf_configuration_map_lookup_elem(&mapSettingKey);
// Use asm("%0 &= 1" : "+r"(match)) before return match,
// to help kernel's bpf verifier, so that it can be 100% certain
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 896bc09..2ec0792 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -24,8 +24,8 @@
#define __kernel_udphdr udphdr
#include <linux/udp.h>
-// The resulting .o needs to load on the Android S bpfloader v0.2
-#define BPFLOADER_MIN_VER 2u
+// The resulting .o needs to load on the Android S bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index c9c73f1..f2fcc8c 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -18,8 +18,8 @@
#include <linux/in.h>
#include <linux/ip.h>
-// The resulting .o needs to load on the Android S bpfloader v0.2
-#define BPFLOADER_MIN_VER 2u
+// The resulting .o needs to load on the Android S bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index 8c32ded..1e508a0 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -103,7 +103,7 @@
// Do not add static_libs to this library: put them in framework-connectivity instead.
// The jarjar rules are only so that references to jarjared utils in
// framework-connectivity-pre-jarjar match at runtime.
- jarjar_rules: ":framework-connectivity-jarjar-rules",
+ jarjar_rules: ":connectivity-jarjar-rules",
permitted_packages: [
"android.app.usage",
"android.net",
diff --git a/framework-t/src/android/net/EthernetManager.java b/framework-t/src/android/net/EthernetManager.java
index 886d194..b8070f0 100644
--- a/framework-t/src/android/net/EthernetManager.java
+++ b/framework-t/src/android/net/EthernetManager.java
@@ -22,13 +22,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Build;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
@@ -573,7 +571,6 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_STACK,
android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
- @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
public void enableInterface(
@NonNull String iface,
@Nullable @CallbackExecutor Executor executor,
@@ -582,7 +579,7 @@
final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
executor, callback);
try {
- mService.connectNetwork(iface, proxy);
+ mService.enableInterface(iface, proxy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -610,7 +607,6 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_STACK,
android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
- @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
public void disableInterface(
@NonNull String iface,
@Nullable @CallbackExecutor Executor executor,
@@ -619,7 +615,7 @@
final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
executor, callback);
try {
- mService.disconnectNetwork(iface, proxy);
+ mService.disableInterface(iface, proxy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/framework-t/src/android/net/IEthernetManager.aidl b/framework-t/src/android/net/IEthernetManager.aidl
index 42e4c1a..c1efc29 100644
--- a/framework-t/src/android/net/IEthernetManager.aidl
+++ b/framework-t/src/android/net/IEthernetManager.aidl
@@ -43,8 +43,8 @@
void releaseTetheredInterface(in ITetheredInterfaceCallback callback);
void updateConfiguration(String iface, in EthernetNetworkUpdateRequest request,
in INetworkInterfaceOutcomeReceiver listener);
- void connectNetwork(String iface, in INetworkInterfaceOutcomeReceiver listener);
- void disconnectNetwork(String iface, in INetworkInterfaceOutcomeReceiver listener);
+ void enableInterface(String iface, in INetworkInterfaceOutcomeReceiver listener);
+ void disableInterface(String iface, in INetworkInterfaceOutcomeReceiver listener);
void setEthernetEnabled(boolean enabled);
List<String> getInterfaceList();
}
diff --git a/framework-t/src/android/net/IpSecManager.java b/framework-t/src/android/net/IpSecManager.java
index 9cb0947..9cceac2 100644
--- a/framework-t/src/android/net/IpSecManager.java
+++ b/framework-t/src/android/net/IpSecManager.java
@@ -817,10 +817,10 @@
* </ol>
*
* @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel.
- * This network MUST never be the network exposing this IpSecTunnelInterface, otherwise
- * this method will throw an {@link IllegalArgumentException}. If the
- * IpSecTunnelInterface is later added to this network, all outbound traffic will be
- * blackholed.
+ * This network MUST be a functional {@link Network} with valid {@link LinkProperties},
+ * and MUST never be the network exposing this IpSecTunnelInterface, otherwise this
+ * method will throw an {@link IllegalArgumentException}. If the IpSecTunnelInterface is
+ * later added to this network, all outbound traffic will be blackholed.
*/
// TODO: b/169171001 Update the documentation when transform migration is supported.
// The purpose of making updating network and applying transforms separate is to leave open
@@ -962,7 +962,6 @@
* IP header and IPsec Header on all inbound traffic).
* <p>Applications should probably not use this API directly.
*
- *
* @param tunnel The {@link IpSecManager#IpSecTunnelInterface} that will use the supplied
* transform.
* @param direction the direction, {@link DIRECTION_OUT} or {@link #DIRECTION_IN} in which
diff --git a/framework-t/src/android/net/NetworkStatsCollection.java b/framework-t/src/android/net/NetworkStatsCollection.java
index 29ea772..6a1d2dd 100644
--- a/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/framework-t/src/android/net/NetworkStatsCollection.java
@@ -865,6 +865,9 @@
* Add association of the history with the specified key in this map.
*
* @param key The object used to identify a network, see {@link Key}.
+ * If history already exists for this key, then the passed-in history is appended
+ * to the previously-passed in history. The caller must ensure that the history
+ * passed-in timestamps are greater than all previously-passed-in timestamps.
* @param history {@link NetworkStatsHistory} instance associated to the given {@link Key}.
* @return The builder object.
*/
@@ -874,9 +877,21 @@
Objects.requireNonNull(key);
Objects.requireNonNull(history);
final List<Entry> historyEntries = history.getEntries();
+ final NetworkStatsHistory existing = mEntries.get(key);
+ final int size = historyEntries.size() + ((existing != null) ? existing.size() : 0);
final NetworkStatsHistory.Builder historyBuilder =
- new NetworkStatsHistory.Builder(mBucketDurationMillis, historyEntries.size());
+ new NetworkStatsHistory.Builder(mBucketDurationMillis, size);
+
+ // TODO: this simply appends the entries to any entries that were already present in
+ // the builder, which requires the caller to pass in entries in order. We might be
+ // able to do better with something like recordHistory.
+ if (existing != null) {
+ for (Entry entry : existing.getEntries()) {
+ historyBuilder.addEntry(entry);
+ }
+ }
+
for (Entry entry : historyEntries) {
historyBuilder.addEntry(entry);
}
diff --git a/framework-t/src/android/net/NetworkStatsHistory.java b/framework-t/src/android/net/NetworkStatsHistory.java
index b45d44d..738e9cc 100644
--- a/framework-t/src/android/net/NetworkStatsHistory.java
+++ b/framework-t/src/android/net/NetworkStatsHistory.java
@@ -32,6 +32,7 @@
import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -57,6 +58,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Random;
+import java.util.TreeMap;
/**
* Collection of historical network statistics, recorded into equally-sized
@@ -252,6 +254,28 @@
+ ", operations=" + operations
+ "}";
}
+
+ /**
+ * Add the given {@link Entry} with this instance and return a new {@link Entry}
+ * instance as the result.
+ *
+ * @hide
+ */
+ @NonNull
+ public Entry plus(@NonNull Entry another, long bucketDuration) {
+ if (this.bucketStart != another.bucketStart) {
+ throw new IllegalArgumentException("bucketStart " + this.bucketStart
+ + " is not equal to " + another.bucketStart);
+ }
+ return new Entry(this.bucketStart,
+ // Active time should not go over bucket duration.
+ Math.min(this.activeTime + another.activeTime, bucketDuration),
+ this.rxBytes + another.rxBytes,
+ this.rxPackets + another.rxPackets,
+ this.txBytes + another.txBytes,
+ this.txPackets + another.txPackets,
+ this.operations + another.operations);
+ }
}
/** @hide */
@@ -949,6 +973,25 @@
return writer.toString();
}
+ /**
+ * Same as "equals", but not actually called equals as this would affect public API behavior.
+ * @hide
+ */
+ @Nullable
+ public boolean isSameAs(NetworkStatsHistory other) {
+ return bucketCount == other.bucketCount
+ && Arrays.equals(bucketStart, other.bucketStart)
+ // Don't check activeTime since it can change on import due to the importer using
+ // recordHistory. It's also not exposed by the APIs or present in dumpsys or
+ // toString().
+ && Arrays.equals(rxBytes, other.rxBytes)
+ && Arrays.equals(rxPackets, other.rxPackets)
+ && Arrays.equals(txBytes, other.txBytes)
+ && Arrays.equals(txPackets, other.txPackets)
+ && Arrays.equals(operations, other.operations)
+ && totalBytes == other.totalBytes;
+ }
+
@UnsupportedAppUsage
public static final @android.annotation.NonNull Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
@Override
@@ -1089,14 +1132,8 @@
* Builder class for {@link NetworkStatsHistory}.
*/
public static final class Builder {
+ private final TreeMap<Long, Entry> mEntries;
private final long mBucketDuration;
- private final List<Long> mBucketStart;
- private final List<Long> mActiveTime;
- private final List<Long> mRxBytes;
- private final List<Long> mRxPackets;
- private final List<Long> mTxBytes;
- private final List<Long> mTxPackets;
- private final List<Long> mOperations;
/**
* Creates a new Builder with given bucket duration and initial capacity to construct
@@ -1107,36 +1144,31 @@
*/
public Builder(long bucketDuration, int initialCapacity) {
mBucketDuration = bucketDuration;
- mBucketStart = new ArrayList<>(initialCapacity);
- mActiveTime = new ArrayList<>(initialCapacity);
- mRxBytes = new ArrayList<>(initialCapacity);
- mRxPackets = new ArrayList<>(initialCapacity);
- mTxBytes = new ArrayList<>(initialCapacity);
- mTxPackets = new ArrayList<>(initialCapacity);
- mOperations = new ArrayList<>(initialCapacity);
+ // Create a collection that is always sorted and can deduplicate items by the timestamp.
+ mEntries = new TreeMap<>();
}
/**
- * Add an {@link Entry} into the {@link NetworkStatsHistory} instance.
+ * Add an {@link Entry} into the {@link NetworkStatsHistory} instance. If the timestamp
+ * already exists, the given {@link Entry} will be combined into existing entry.
*
* @param entry The target {@link Entry} object.
* @return The builder object.
*/
@NonNull
public Builder addEntry(@NonNull Entry entry) {
- mBucketStart.add(entry.bucketStart);
- mActiveTime.add(entry.activeTime);
- mRxBytes.add(entry.rxBytes);
- mRxPackets.add(entry.rxPackets);
- mTxBytes.add(entry.txBytes);
- mTxPackets.add(entry.txPackets);
- mOperations.add(entry.operations);
+ final Entry existing = mEntries.get(entry.bucketStart);
+ if (existing != null) {
+ mEntries.put(entry.bucketStart, existing.plus(entry, mBucketDuration));
+ } else {
+ mEntries.put(entry.bucketStart, entry);
+ }
return this;
}
- private static long sum(@NonNull List<Long> list) {
- long sum = 0;
- for (long entry : list) {
+ private static long sum(@NonNull long[] array) {
+ long sum = 0L;
+ for (long entry : array) {
sum += entry;
}
return sum;
@@ -1149,16 +1181,30 @@
*/
@NonNull
public NetworkStatsHistory build() {
- return new NetworkStatsHistory(mBucketDuration,
- CollectionUtils.toLongArray(mBucketStart),
- CollectionUtils.toLongArray(mActiveTime),
- CollectionUtils.toLongArray(mRxBytes),
- CollectionUtils.toLongArray(mRxPackets),
- CollectionUtils.toLongArray(mTxBytes),
- CollectionUtils.toLongArray(mTxPackets),
- CollectionUtils.toLongArray(mOperations),
- mBucketStart.size(),
- sum(mRxBytes) + sum(mTxBytes));
+ int size = mEntries.size();
+ final long[] bucketStart = new long[size];
+ final long[] activeTime = new long[size];
+ final long[] rxBytes = new long[size];
+ final long[] rxPackets = new long[size];
+ final long[] txBytes = new long[size];
+ final long[] txPackets = new long[size];
+ final long[] operations = new long[size];
+
+ int i = 0;
+ for (Entry entry : mEntries.values()) {
+ bucketStart[i] = entry.bucketStart;
+ activeTime[i] = entry.activeTime;
+ rxBytes[i] = entry.rxBytes;
+ rxPackets[i] = entry.rxPackets;
+ txBytes[i] = entry.txBytes;
+ txPackets[i] = entry.txPackets;
+ operations[i] = entry.operations;
+ i++;
+ }
+
+ return new NetworkStatsHistory(mBucketDuration, bucketStart, activeTime,
+ rxBytes, rxPackets, txBytes, txPackets, operations,
+ size, sum(rxBytes) + sum(txBytes));
}
}
}
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 33b44c8..fad63e5 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -175,6 +175,7 @@
*
* @see #ACTION_NSD_STATE_CHANGED
*/
+ // TODO: Deprecate this since NSD service is never disabled.
public static final int NSD_STATE_DISABLED = 1;
/**
@@ -230,17 +231,12 @@
public static final int DAEMON_STARTUP = 19;
/** @hide */
- public static final int ENABLE = 20;
- /** @hide */
- public static final int DISABLE = 21;
+ public static final int MDNS_SERVICE_EVENT = 20;
/** @hide */
- public static final int MDNS_SERVICE_EVENT = 22;
-
+ public static final int REGISTER_CLIENT = 21;
/** @hide */
- public static final int REGISTER_CLIENT = 23;
- /** @hide */
- public static final int UNREGISTER_CLIENT = 24;
+ public static final int UNREGISTER_CLIENT = 22;
/** Dns based service discovery protocol */
public static final int PROTOCOL_DNS_SD = 0x0001;
@@ -266,8 +262,6 @@
EVENT_NAMES.put(RESOLVE_SERVICE_SUCCEEDED, "RESOLVE_SERVICE_SUCCEEDED");
EVENT_NAMES.put(DAEMON_CLEANUP, "DAEMON_CLEANUP");
EVENT_NAMES.put(DAEMON_STARTUP, "DAEMON_STARTUP");
- EVENT_NAMES.put(ENABLE, "ENABLE");
- EVENT_NAMES.put(DISABLE, "DISABLE");
EVENT_NAMES.put(MDNS_SERVICE_EVENT, "MDNS_SERVICE_EVENT");
}
@@ -312,9 +306,12 @@
@Override
public void onAvailable(@NonNull Network network) {
final DelegatingDiscoveryListener wrappedListener = new DelegatingDiscoveryListener(
- network, mBaseListener);
+ network, mBaseListener, mBaseExecutor);
mPerNetworkListeners.put(network, wrappedListener);
- discoverServices(mServiceType, mProtocolType, network, mBaseExecutor,
+ // Run discovery callbacks inline on the service handler thread, which is the
+ // same thread used by this NetworkCallback, but DelegatingDiscoveryListener will
+ // use the base executor to run the wrapped callbacks.
+ discoverServices(mServiceType, mProtocolType, network, Runnable::run,
wrappedListener);
}
@@ -334,7 +331,8 @@
public void start(@NonNull NetworkRequest request) {
final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
cm.registerNetworkCallback(request, mNetworkCb, mHandler);
- mHandler.post(() -> mBaseListener.onDiscoveryStarted(mServiceType));
+ mHandler.post(() -> mBaseExecutor.execute(() ->
+ mBaseListener.onDiscoveryStarted(mServiceType)));
}
/**
@@ -351,7 +349,7 @@
final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
cm.unregisterNetworkCallback(mNetworkCb);
if (mPerNetworkListeners.size() == 0) {
- mBaseListener.onDiscoveryStopped(mServiceType);
+ mBaseExecutor.execute(() -> mBaseListener.onDiscoveryStopped(mServiceType));
return;
}
for (int i = 0; i < mPerNetworkListeners.size(); i++) {
@@ -399,14 +397,23 @@
}
}
+ /**
+ * A listener wrapping calls to an app-provided listener, while keeping track of found
+ * services, so they can all be reported lost when the underlying network is lost.
+ *
+ * This should be registered to run on the service handler.
+ */
private class DelegatingDiscoveryListener implements DiscoveryListener {
private final Network mNetwork;
private final DiscoveryListener mWrapped;
+ private final Executor mWrappedExecutor;
private final ArraySet<TrackedNsdInfo> mFoundInfo = new ArraySet<>();
- private DelegatingDiscoveryListener(Network network, DiscoveryListener listener) {
+ private DelegatingDiscoveryListener(Network network, DiscoveryListener listener,
+ Executor executor) {
mNetwork = network;
mWrapped = listener;
+ mWrappedExecutor = executor;
}
void notifyAllServicesLost() {
@@ -415,7 +422,7 @@
final NsdServiceInfo serviceInfo = new NsdServiceInfo(
trackedInfo.mServiceName, trackedInfo.mServiceType);
serviceInfo.setNetwork(mNetwork);
- mWrapped.onServiceLost(serviceInfo);
+ mWrappedExecutor.execute(() -> mWrapped.onServiceLost(serviceInfo));
}
}
@@ -444,7 +451,7 @@
// Do not report onStopDiscoveryFailed when some underlying listeners failed:
// this does not mean that all listeners did, and onStopDiscoveryFailed is not
// actionable anyway. Just report that discovery stopped.
- mWrapped.onDiscoveryStopped(serviceType);
+ mWrappedExecutor.execute(() -> mWrapped.onDiscoveryStopped(serviceType));
}
}
@@ -452,20 +459,20 @@
public void onDiscoveryStopped(String serviceType) {
mPerNetworkListeners.remove(mNetwork);
if (mStopRequested && mPerNetworkListeners.size() == 0) {
- mWrapped.onDiscoveryStopped(serviceType);
+ mWrappedExecutor.execute(() -> mWrapped.onDiscoveryStopped(serviceType));
}
}
@Override
public void onServiceFound(NsdServiceInfo serviceInfo) {
mFoundInfo.add(new TrackedNsdInfo(serviceInfo));
- mWrapped.onServiceFound(serviceInfo);
+ mWrappedExecutor.execute(() -> mWrapped.onServiceFound(serviceInfo));
}
@Override
public void onServiceLost(NsdServiceInfo serviceInfo) {
mFoundInfo.remove(new TrackedNsdInfo(serviceInfo));
- mWrapped.onServiceLost(serviceInfo);
+ mWrappedExecutor.execute(() -> mWrapped.onServiceLost(serviceInfo));
}
}
}
@@ -648,8 +655,12 @@
@Override
public void handleMessage(Message message) {
+ // Do not use message in the executor lambdas, as it will be recycled once this method
+ // returns. Keep references to its content instead.
final int what = message.what;
+ final int errorCode = message.arg1;
final int key = message.arg2;
+ final Object obj = message.obj;
final Object listener;
final NsdServiceInfo ns;
final Executor executor;
@@ -659,7 +670,7 @@
executor = mExecutorMap.get(key);
}
if (listener == null) {
- Log.d(TAG, "Stale key " + message.arg2);
+ Log.d(TAG, "Stale key " + key);
return;
}
if (DBG) {
@@ -667,28 +678,28 @@
}
switch (what) {
case DISCOVER_SERVICES_STARTED:
- final String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
+ final String s = getNsdServiceInfoType((NsdServiceInfo) obj);
executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStarted(s));
break;
case DISCOVER_SERVICES_FAILED:
removeListener(key);
executor.execute(() -> ((DiscoveryListener) listener).onStartDiscoveryFailed(
- getNsdServiceInfoType(ns), message.arg1));
+ getNsdServiceInfoType(ns), errorCode));
break;
case SERVICE_FOUND:
executor.execute(() -> ((DiscoveryListener) listener).onServiceFound(
- (NsdServiceInfo) message.obj));
+ (NsdServiceInfo) obj));
break;
case SERVICE_LOST:
executor.execute(() -> ((DiscoveryListener) listener).onServiceLost(
- (NsdServiceInfo) message.obj));
+ (NsdServiceInfo) obj));
break;
case STOP_DISCOVERY_FAILED:
// TODO: failure to stop discovery should be internal and retried internally, as
// the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
removeListener(key);
executor.execute(() -> ((DiscoveryListener) listener).onStopDiscoveryFailed(
- getNsdServiceInfoType(ns), message.arg1));
+ getNsdServiceInfoType(ns), errorCode));
break;
case STOP_DISCOVERY_SUCCEEDED:
removeListener(key);
@@ -698,33 +709,33 @@
case REGISTER_SERVICE_FAILED:
removeListener(key);
executor.execute(() -> ((RegistrationListener) listener).onRegistrationFailed(
- ns, message.arg1));
+ ns, errorCode));
break;
case REGISTER_SERVICE_SUCCEEDED:
executor.execute(() -> ((RegistrationListener) listener).onServiceRegistered(
- (NsdServiceInfo) message.obj));
+ (NsdServiceInfo) obj));
break;
case UNREGISTER_SERVICE_FAILED:
removeListener(key);
executor.execute(() -> ((RegistrationListener) listener).onUnregistrationFailed(
- ns, message.arg1));
+ ns, errorCode));
break;
case UNREGISTER_SERVICE_SUCCEEDED:
// TODO: do not unregister listener until service is unregistered, or provide
// alternative way for unregistering ?
- removeListener(message.arg2);
+ removeListener(key);
executor.execute(() -> ((RegistrationListener) listener).onServiceUnregistered(
ns));
break;
case RESOLVE_SERVICE_FAILED:
removeListener(key);
executor.execute(() -> ((ResolveListener) listener).onResolveFailed(
- ns, message.arg1));
+ ns, errorCode));
break;
case RESOLVE_SERVICE_SUCCEEDED:
removeListener(key);
executor.execute(() -> ((ResolveListener) listener).onServiceResolved(
- (NsdServiceInfo) message.obj));
+ (NsdServiceInfo) obj));
break;
default:
Log.d(TAG, "Ignored " + message);
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index 2621594..200c808 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -53,6 +53,8 @@
@Nullable
private Network mNetwork;
+ private int mInterfaceIndex;
+
public NsdServiceInfo() {
}
@@ -312,8 +314,11 @@
/**
* Get the network where the service can be found.
*
- * This is never null if this {@link NsdServiceInfo} was obtained from
- * {@link NsdManager#discoverServices} or {@link NsdManager#resolveService}.
+ * This is set if this {@link NsdServiceInfo} was obtained from
+ * {@link NsdManager#discoverServices} or {@link NsdManager#resolveService}, unless the service
+ * was found on a network interface that does not have a {@link Network} (such as a tethering
+ * downstream, where services are advertised from devices connected to this device via
+ * tethering).
*/
@Nullable
public Network getNetwork() {
@@ -329,6 +334,26 @@
mNetwork = network;
}
+ /**
+ * Get the index of the network interface where the service was found.
+ *
+ * This is only set when the service was found on an interface that does not have a usable
+ * Network, in which case {@link #getNetwork()} returns null.
+ * @return The interface index as per {@link java.net.NetworkInterface#getIndex}, or 0 if unset.
+ * @hide
+ */
+ public int getInterfaceIndex() {
+ return mInterfaceIndex;
+ }
+
+ /**
+ * Set the index of the network interface where the service was found.
+ * @hide
+ */
+ public void setInterfaceIndex(int interfaceIndex) {
+ mInterfaceIndex = interfaceIndex;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -375,6 +400,7 @@
}
dest.writeParcelable(mNetwork, 0);
+ dest.writeInt(mInterfaceIndex);
}
/** Implement the Parcelable interface */
@@ -405,6 +431,7 @@
info.mTxtRecord.put(in.readString(), valueArray);
}
info.mNetwork = in.readParcelable(null, Network.class);
+ info.mInterfaceIndex = in.readInt();
return info;
}
diff --git a/framework/Android.bp b/framework/Android.bp
index c8b64c7..d7de439 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -111,7 +111,6 @@
// because the tethering stubs depend on the connectivity stubs (e.g.,
// TetheringRequest depends on LinkAddress).
"framework-tethering.stubs.module_lib",
- "framework-wifi.stubs.module_lib",
],
visibility: ["//packages/modules/Connectivity:__subpackages__"]
}
@@ -120,7 +119,7 @@
name: "framework-connectivity",
defaults: ["framework-connectivity-defaults"],
installable: true,
- jarjar_rules: ":framework-connectivity-jarjar-rules",
+ jarjar_rules: ":connectivity-jarjar-rules",
permitted_packages: ["android.net"],
impl_library_visibility: [
"//packages/modules/Connectivity/Tethering/apex",
@@ -223,35 +222,3 @@
],
output_extension: "srcjar",
}
-
-java_genrule {
- name: "framework-connectivity-jarjar-rules",
- tool_files: [
- ":connectivity-hiddenapi-files",
- ":framework-connectivity-pre-jarjar",
- ":framework-connectivity-t-pre-jarjar",
- ":framework-connectivity.stubs.module_lib",
- ":framework-connectivity-t.stubs.module_lib",
- "jarjar-excludes.txt",
- ],
- tools: [
- "jarjar-rules-generator",
- "dexdump",
- ],
- out: ["framework_connectivity_jarjar_rules.txt"],
- cmd: "$(location jarjar-rules-generator) " +
- "--jars $(location :framework-connectivity-pre-jarjar) " +
- "$(location :framework-connectivity-t-pre-jarjar) " +
- "--prefix android.net.connectivity " +
- "--apistubs $(location :framework-connectivity.stubs.module_lib) " +
- "$(location :framework-connectivity-t.stubs.module_lib) " +
- "--unsupportedapi $(locations :connectivity-hiddenapi-files) " +
- "--excludes $(location jarjar-excludes.txt) " +
- "--dexdump $(location dexdump) " +
- "--output $(out)",
- visibility: [
- "//packages/modules/Connectivity/framework:__subpackages__",
- "//packages/modules/Connectivity/framework-t:__subpackages__",
- "//packages/modules/Connectivity/service",
- ],
-}
diff --git a/framework/jarjar-excludes.txt b/framework/jarjar-excludes.txt
deleted file mode 100644
index 1311765..0000000
--- a/framework/jarjar-excludes.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-# INetworkStatsProvider / INetworkStatsProviderCallback are referenced from net-tests-utils, which
-# may be used by tests that do not apply connectivity jarjar rules.
-# TODO: move files to a known internal package (like android.net.connectivity.visiblefortesting)
-# so that they do not need jarjar
-android\.net\.netstats\.provider\.INetworkStatsProvider(\$.+)?
-android\.net\.netstats\.provider\.INetworkStatsProviderCallback(\$.+)?
-
-# INetworkAgent / INetworkAgentRegistry are used in NetworkAgentTest
-# TODO: move files to android.net.connectivity.visiblefortesting
-android\.net\.INetworkAgent(\$.+)?
-android\.net\.INetworkAgentRegistry(\$.+)?
-
-# IConnectivityDiagnosticsCallback used in ConnectivityDiagnosticsManagerTest
-# TODO: move files to android.net.connectivity.visiblefortesting
-android\.net\.IConnectivityDiagnosticsCallback(\$.+)?
-
-
-# KeepaliveUtils is used by ConnectivityManager CTS
-# TODO: move into service-connectivity so framework-connectivity stops using
-# ServiceConnectivityResources (callers need high permissions to find/query the resource apk anyway)
-# and have a ConnectivityManager test API instead
-android\.net\.util\.KeepaliveUtils(\$.+)?
-
-# TODO (b/217115866): add jarjar rules for Nearby
-android\.nearby\..+
diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp
index 857ece5..7478b3e 100644
--- a/framework/jni/android_net_NetworkUtils.cpp
+++ b/framework/jni/android_net_NetworkUtils.cpp
@@ -232,8 +232,7 @@
return NULL;
}
- jclass class_TcpRepairWindow = env->FindClass(
- "android/net/connectivity/android/net/TcpRepairWindow");
+ jclass class_TcpRepairWindow = env->FindClass("android/net/TcpRepairWindow");
jmethodID ctor = env->GetMethodID(class_TcpRepairWindow, "<init>", "(IIIIII)V");
return env->NewObject(class_TcpRepairWindow, ctor, trw.snd_wl1, trw.snd_wnd, trw.max_window,
@@ -254,7 +253,7 @@
{ "bindSocketToNetworkHandle", "(Ljava/io/FileDescriptor;J)I", (void*) android_net_utils_bindSocketToNetworkHandle },
{ "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
{ "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
- { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/connectivity/android/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
+ { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
{ "resNetworkSend", "(J[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
{ "resNetworkQuery", "(JLjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
{ "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 9f9ee95..5769b92 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -992,6 +992,27 @@
*/
public static final int FIREWALL_CHAIN_LOCKDOWN_VPN = 6;
+ /**
+ * Firewall chain used for OEM-specific application restrictions.
+ * Denylist of apps that will not have network access due to OEM-specific restrictions.
+ * @hide
+ */
+ public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7;
+
+ /**
+ * Firewall chain used for OEM-specific application restrictions.
+ * Denylist of apps that will not have network access due to OEM-specific restrictions.
+ * @hide
+ */
+ public static final int FIREWALL_CHAIN_OEM_DENY_2 = 8;
+
+ /**
+ * Firewall chain used for OEM-specific application restrictions.
+ * Denylist of apps that will not have network access due to OEM-specific restrictions.
+ * @hide
+ */
+ public static final int FIREWALL_CHAIN_OEM_DENY_3 = 9;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = false, prefix = "FIREWALL_CHAIN_", value = {
@@ -1000,7 +1021,10 @@
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_RESTRICTED,
FIREWALL_CHAIN_LOW_POWER_STANDBY,
- FIREWALL_CHAIN_LOCKDOWN_VPN
+ FIREWALL_CHAIN_LOCKDOWN_VPN,
+ FIREWALL_CHAIN_OEM_DENY_1,
+ FIREWALL_CHAIN_OEM_DENY_2,
+ FIREWALL_CHAIN_OEM_DENY_3
})
public @interface FirewallChain {}
// LINT.ThenChange(packages/modules/Connectivity/service/native/include/Common.h)
diff --git a/framework/src/android/net/DnsResolverServiceManager.java b/framework/src/android/net/DnsResolverServiceManager.java
index e64d2ae..79009e8 100644
--- a/framework/src/android/net/DnsResolverServiceManager.java
+++ b/framework/src/android/net/DnsResolverServiceManager.java
@@ -29,7 +29,7 @@
private final IBinder mResolver;
- public DnsResolverServiceManager(IBinder resolver) {
+ DnsResolverServiceManager(IBinder resolver) {
mResolver = resolver;
}
diff --git a/framework/src/android/net/NattSocketKeepalive.java b/framework/src/android/net/NattSocketKeepalive.java
index 56cc923..a15d165 100644
--- a/framework/src/android/net/NattSocketKeepalive.java
+++ b/framework/src/android/net/NattSocketKeepalive.java
@@ -33,7 +33,7 @@
@NonNull private final InetAddress mDestination;
private final int mResourceId;
- public NattSocketKeepalive(@NonNull IConnectivityManager service,
+ NattSocketKeepalive(@NonNull IConnectivityManager service,
@NonNull Network network,
@NonNull ParcelFileDescriptor pfd,
int resourceId,
@@ -48,7 +48,7 @@
}
@Override
- protected void startImpl(int intervalSec) {
+ void startImpl(int intervalSec) {
mExecutor.execute(() -> {
try {
mService.startNattKeepaliveWithFd(mNetwork, mPfd, mResourceId,
@@ -62,7 +62,7 @@
}
@Override
- protected void stopImpl() {
+ void stopImpl() {
mExecutor.execute(() -> {
try {
if (mSlot != null) {
diff --git a/framework/src/android/net/QosCallbackConnection.java b/framework/src/android/net/QosCallbackConnection.java
index cfceddd..de0fc24 100644
--- a/framework/src/android/net/QosCallbackConnection.java
+++ b/framework/src/android/net/QosCallbackConnection.java
@@ -35,7 +35,7 @@
*
* @hide
*/
-public class QosCallbackConnection extends android.net.IQosCallback.Stub {
+class QosCallbackConnection extends android.net.IQosCallback.Stub {
@NonNull private final ConnectivityManager mConnectivityManager;
@Nullable private volatile QosCallback mCallback;
@@ -56,7 +56,7 @@
* {@link Executor} must run callback sequentially, otherwise the order of
* callbacks cannot be guaranteed.
*/
- public QosCallbackConnection(@NonNull final ConnectivityManager connectivityManager,
+ QosCallbackConnection(@NonNull final ConnectivityManager connectivityManager,
@NonNull final QosCallback callback,
@NonNull final Executor executor) {
mConnectivityManager = Objects.requireNonNull(connectivityManager,
@@ -142,7 +142,7 @@
* There are no synchronization guarantees on exactly when the callback will stop receiving
* messages.
*/
- public void stopReceivingMessages() {
+ void stopReceivingMessages() {
mCallback = null;
}
}
diff --git a/framework/src/android/net/QosCallbackException.java b/framework/src/android/net/QosCallbackException.java
index 9e5d98a..b80cff4 100644
--- a/framework/src/android/net/QosCallbackException.java
+++ b/framework/src/android/net/QosCallbackException.java
@@ -85,7 +85,7 @@
* {@hide}
*/
@NonNull
- public static QosCallbackException createException(@ExceptionType final int type) {
+ static QosCallbackException createException(@ExceptionType final int type) {
switch (type) {
case EX_TYPE_FILTER_NETWORK_RELEASED:
return new QosCallbackException(new NetworkReleasedException());
diff --git a/framework/src/android/net/QosFilter.java b/framework/src/android/net/QosFilter.java
index 01dc4bb..b432644 100644
--- a/framework/src/android/net/QosFilter.java
+++ b/framework/src/android/net/QosFilter.java
@@ -33,15 +33,13 @@
@SystemApi
public abstract class QosFilter {
- /** @hide */
- protected QosFilter() {
- // Ensure that all derived types are known, and known to be properly handled when being
- // passed to and from NetworkAgent.
- // For now the only known derived type is QosSocketFilter.
- if (!(this instanceof QosSocketFilter)) {
- throw new UnsupportedOperationException(
- "Unsupported QosFilter type: " + this.getClass().getName());
- }
+ /**
+ * The constructor is kept hidden from outside this package to ensure that all derived types
+ * are known and properly handled when being passed to and from {@link NetworkAgent}.
+ *
+ * @hide
+ */
+ QosFilter() {
}
/**
diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java
index da9b356..49ac22b 100644
--- a/framework/src/android/net/QosSocketInfo.java
+++ b/framework/src/android/net/QosSocketInfo.java
@@ -73,10 +73,9 @@
* The parcel file descriptor wrapped around the socket's file descriptor.
*
* @return the parcel file descriptor of the socket
- * @hide
*/
@NonNull
- public ParcelFileDescriptor getParcelFileDescriptor() {
+ ParcelFileDescriptor getParcelFileDescriptor() {
return mParcelFileDescriptor;
}
diff --git a/framework/src/android/net/SocketKeepalive.java b/framework/src/android/net/SocketKeepalive.java
index 57cf5e3..f6cae72 100644
--- a/framework/src/android/net/SocketKeepalive.java
+++ b/framework/src/android/net/SocketKeepalive.java
@@ -52,8 +52,7 @@
* request. If it does, it MUST support at least 3 concurrent keepalive slots.
*/
public abstract class SocketKeepalive implements AutoCloseable {
- /** @hide */
- protected static final String TAG = "SocketKeepalive";
+ static final String TAG = "SocketKeepalive";
/**
* Success. It indicates there is no error.
@@ -216,22 +215,15 @@
}
}
- /** @hide */
- @NonNull protected final IConnectivityManager mService;
- /** @hide */
- @NonNull protected final Network mNetwork;
- /** @hide */
- @NonNull protected final ParcelFileDescriptor mPfd;
- /** @hide */
- @NonNull protected final Executor mExecutor;
- /** @hide */
- @NonNull protected final ISocketKeepaliveCallback mCallback;
+ @NonNull final IConnectivityManager mService;
+ @NonNull final Network mNetwork;
+ @NonNull final ParcelFileDescriptor mPfd;
+ @NonNull final Executor mExecutor;
+ @NonNull final ISocketKeepaliveCallback mCallback;
// TODO: remove slot since mCallback could be used to identify which keepalive to stop.
- /** @hide */
- @Nullable protected Integer mSlot;
+ @Nullable Integer mSlot;
- /** @hide */
- public SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
+ SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
@NonNull ParcelFileDescriptor pfd,
@NonNull Executor executor, @NonNull Callback callback) {
mService = service;
@@ -311,8 +303,7 @@
startImpl(intervalSec);
}
- /** @hide */
- protected abstract void startImpl(int intervalSec);
+ abstract void startImpl(int intervalSec);
/**
* Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped}
@@ -322,8 +313,7 @@
stopImpl();
}
- /** @hide */
- protected abstract void stopImpl();
+ abstract void stopImpl();
/**
* Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be
diff --git a/framework/src/android/net/TcpSocketKeepalive.java b/framework/src/android/net/TcpSocketKeepalive.java
index 7131784..d89814d 100644
--- a/framework/src/android/net/TcpSocketKeepalive.java
+++ b/framework/src/android/net/TcpSocketKeepalive.java
@@ -24,9 +24,9 @@
import java.util.concurrent.Executor;
/** @hide */
-public final class TcpSocketKeepalive extends SocketKeepalive {
+final class TcpSocketKeepalive extends SocketKeepalive {
- public TcpSocketKeepalive(@NonNull IConnectivityManager service,
+ TcpSocketKeepalive(@NonNull IConnectivityManager service,
@NonNull Network network,
@NonNull ParcelFileDescriptor pfd,
@NonNull Executor executor,
@@ -50,7 +50,7 @@
* acknowledgement.
*/
@Override
- protected void startImpl(int intervalSec) {
+ void startImpl(int intervalSec) {
mExecutor.execute(() -> {
try {
mService.startTcpKeepalive(mNetwork, mPfd, intervalSec, mCallback);
@@ -62,7 +62,7 @@
}
@Override
- protected void stopImpl() {
+ void stopImpl() {
mExecutor.execute(() -> {
try {
if (mSlot != null) {
diff --git a/netd/Android.bp b/netd/Android.bp
index 5ac02d3..c731b8b 100644
--- a/netd/Android.bp
+++ b/netd/Android.bp
@@ -55,7 +55,8 @@
cc_test {
name: "netd_updatable_unit_test",
defaults: ["netd_defaults"],
- test_suites: ["general-tests"],
+ test_suites: ["general-tests", "mts-tethering"],
+ test_config_template: ":net_native_test_config_template",
require_root: true, // required by setrlimitForTest()
header_libs: [
"bpf_connectivity_headers",
@@ -72,6 +73,7 @@
"liblog",
"libnetdutils",
],
+ compile_multilib: "both",
multilib: {
lib32: {
suffix: "32",
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 08e66d9..42d0de5 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -104,6 +104,7 @@
RETURN_IF_NOT_OK(mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, SELECT_MAP_A,
BPF_ANY));
RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+ ALOGI("%s successfully", __func__);
return netdutils::status::ok;
}
@@ -233,7 +234,7 @@
if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
base::Result<void> res = mCookieTagMap.deleteValue(sock_cookie);
if (!res.ok()) {
- ALOGE("Failed to untag socket: %s\n", strerror(res.error().code()));
+ ALOGE("Failed to untag socket: %s", strerror(res.error().code()));
return -res.error().code();
}
return 0;
diff --git a/netd/BpfHandler.h b/netd/BpfHandler.h
index 2ede1c1..05b9ebc 100644
--- a/netd/BpfHandler.h
+++ b/netd/BpfHandler.h
@@ -62,7 +62,7 @@
BpfMap<uint64_t, UidTagValue> mCookieTagMap;
BpfMap<StatsKey, StatsValue> mStatsMapA;
BpfMap<StatsKey, StatsValue> mStatsMapB;
- BpfMap<uint32_t, uint8_t> mConfigurationMap;
+ BpfMap<uint32_t, uint32_t> mConfigurationMap;
BpfMap<uint32_t, uint8_t> mUidPermissionMap;
std::mutex mMutex;
diff --git a/netd/BpfHandlerTest.cpp b/netd/BpfHandlerTest.cpp
index cd6b565..1bd222d 100644
--- a/netd/BpfHandlerTest.cpp
+++ b/netd/BpfHandlerTest.cpp
@@ -21,6 +21,7 @@
#include <gtest/gtest.h>
+#define TEST_BPF_MAP
#include "BpfHandler.h"
using namespace android::bpf; // NOLINT(google-build-using-namespace): exempted
@@ -48,46 +49,38 @@
BpfHandler mBh;
BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
- BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
+ BpfMap<uint32_t, uint32_t> mFakeConfigurationMap;
BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
void SetUp() {
std::lock_guard guard(mBh.mMutex);
ASSERT_EQ(0, setrlimitForTest());
- mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(UidTagValue),
- TEST_MAP_SIZE, 0));
+ mFakeCookieTagMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeCookieTagMap);
- mFakeStatsMapA.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(StatsKey), sizeof(StatsValue),
- TEST_MAP_SIZE, 0));
+ mFakeStatsMapA.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeStatsMapA);
- mFakeConfigurationMap.reset(
- createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), 1, 0));
+ mFakeConfigurationMap.resetMap(BPF_MAP_TYPE_HASH, 1);
ASSERT_VALID(mFakeConfigurationMap);
- mFakeUidPermissionMap.reset(
- createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+ mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
ASSERT_VALID(mFakeUidPermissionMap);
- mBh.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
+ mBh.mCookieTagMap = mFakeCookieTagMap;
ASSERT_VALID(mBh.mCookieTagMap);
- mBh.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
+ mBh.mStatsMapA = mFakeStatsMapA;
ASSERT_VALID(mBh.mStatsMapA);
- mBh.mConfigurationMap.reset(dupFd(mFakeConfigurationMap.getMap()));
+ mBh.mConfigurationMap = mFakeConfigurationMap;
ASSERT_VALID(mBh.mConfigurationMap);
// Always write to stats map A by default.
ASSERT_RESULT_OK(mBh.mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY,
SELECT_MAP_A, BPF_ANY));
- mBh.mUidPermissionMap.reset(dupFd(mFakeUidPermissionMap.getMap()));
+ mBh.mUidPermissionMap = mFakeUidPermissionMap;
ASSERT_VALID(mBh.mUidPermissionMap);
}
- int dupFd(const android::base::unique_fd& mapFd) {
- return fcntl(mapFd.get(), F_DUPFD_CLOEXEC, 0);
- }
-
int setUpSocketAndTag(int protocol, uint64_t* cookie, uint32_t tag, uid_t uid,
uid_t realUid) {
int sock = socket(protocol, SOCK_STREAM | SOCK_CLOEXEC, 0);
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index bf56fd5..5b3d314 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -48,7 +48,8 @@
cc_test {
name: "libnetworkstats_test",
- test_suites: ["general-tests"],
+ test_suites: ["general-tests", "mts-tethering"],
+ test_config_template: ":net_native_test_config_template",
require_root: true, // required by setrlimitForTest()
header_libs: ["bpf_connectivity_headers"],
srcs: [
@@ -68,4 +69,13 @@
"libbase",
"liblog",
],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
}
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index 4d605ce..c67821f 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -193,7 +193,7 @@
return ret;
}
- BpfMapRO<uint32_t, uint8_t> configurationMap(CONFIGURATION_MAP_PATH);
+ BpfMapRO<uint32_t, uint32_t> configurationMap(CONFIGURATION_MAP_PATH);
if (!configurationMap.isValid()) {
int ret = -errno;
ALOGE("get configuration map fd failed: %s", strerror(errno));
@@ -205,6 +205,10 @@
configuration.error().message().c_str());
return -configuration.error().code();
}
+ if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
+ ALOGE("%s unknown configuration value: %d", __func__, configuration.value());
+ return -EINVAL;
+ }
const char* statsMapPath = STATS_MAP_PATH[configuration.value()];
BpfMap<StatsKey, StatsValue> statsMap(statsMapPath);
if (!statsMap.isValid()) {
diff --git a/service-t/src/com/android/server/IpSecService.java b/service-t/src/com/android/server/IpSecService.java
index 4bc40ea..16b9f1e 100644
--- a/service-t/src/com/android/server/IpSecService.java
+++ b/service-t/src/com/android/server/IpSecService.java
@@ -1452,6 +1452,11 @@
final ConnectivityManager connectivityManager =
mContext.getSystemService(ConnectivityManager.class);
final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork);
+ if (lp == null) {
+ throw new IllegalArgumentException(
+ "LinkProperties is null. The underlyingNetwork may not be functional");
+ }
+
if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) {
throw new IllegalArgumentException(
"Underlying network cannot be the network being exposed by this tunnel");
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index ea57bac..7115720 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
+import android.net.INetd;
import android.net.LinkProperties;
import android.net.Network;
import android.net.mdns.aidl.DiscoveryInfo;
@@ -100,7 +101,6 @@
private class NsdStateMachine extends StateMachine {
private final DefaultState mDefaultState = new DefaultState();
- private final DisabledState mDisabledState = new DisabledState();
private final EnabledState mEnabledState = new EnabledState();
@Override
@@ -151,7 +151,6 @@
NsdStateMachine(String name, Handler handler) {
super(name, handler);
addState(mDefaultState);
- addState(mDisabledState, mDefaultState);
addState(mEnabledState, mDefaultState);
State initialState = mEnabledState;
setInitialState(initialState);
@@ -249,25 +248,6 @@
}
}
- class DisabledState extends State {
- @Override
- public void enter() {
- sendNsdStateChangeBroadcast(false);
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case NsdManager.ENABLE:
- transitionTo(mEnabledState);
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
class EnabledState extends State {
@Override
public void enter() {
@@ -311,10 +291,6 @@
final int clientId = msg.arg2;
final ListenerArgs args;
switch (msg.what) {
- case NsdManager.DISABLE:
- //TODO: cleanup clients
- transitionTo(mDisabledState);
- break;
case NsdManager.DISCOVER_SERVICES:
if (DBG) Log.d(TAG, "Discover services");
args = (ListenerArgs) msg.obj;
@@ -466,7 +442,7 @@
// interfaces that do not have an associated Network.
break;
}
- servInfo.setNetwork(new Network(foundNetId));
+ setServiceNetworkForCallback(servInfo, info.netId, info.interfaceIdx);
clientInfo.onServiceFound(clientId, servInfo);
break;
}
@@ -476,10 +452,11 @@
final String type = info.registrationType;
final int lostNetId = info.netId;
servInfo = new NsdServiceInfo(name, type);
- // The network could be null if it was torn down when the service is lost
- // TODO: avoid returning null in that case, possibly by remembering found
- // services on the same interface index and their network at the time
- servInfo.setNetwork(lostNetId == 0 ? null : new Network(lostNetId));
+ // The network could be set to null (netId 0) if it was torn down when the
+ // service is lost
+ // TODO: avoid returning null in that case, possibly by remembering
+ // found services on the same interface index and their network at the time
+ setServiceNetworkForCallback(servInfo, lostNetId, info.interfaceIdx);
clientInfo.onServiceLost(clientId, servInfo);
break;
}
@@ -513,7 +490,7 @@
break;
}
- String name = fullName.substring(0, index);
+ String name = unescape(fullName.substring(0, index));
String rest = fullName.substring(index);
String type = rest.replace(".local.", "");
@@ -557,7 +534,6 @@
final GetAddressInfo info = (GetAddressInfo) obj;
final String address = info.address;
final int netId = info.netId;
- final Network network = netId == NETID_UNSET ? null : new Network(netId);
InetAddress serviceHost = null;
try {
serviceHost = InetAddress.getByName(address);
@@ -568,9 +544,10 @@
// If the resolved service is on an interface without a network, consider it
// as a failure: it would not be usable by apps as they would need
// privileged permissions.
- if (network != null && serviceHost != null) {
+ if (netId != NETID_UNSET && serviceHost != null) {
clientInfo.mResolvedService.setHost(serviceHost);
- clientInfo.mResolvedService.setNetwork(network);
+ setServiceNetworkForCallback(clientInfo.mResolvedService,
+ netId, info.interfaceIdx);
clientInfo.onResolveServiceSucceeded(
clientId, clientInfo.mResolvedService);
} else {
@@ -590,6 +567,55 @@
}
}
+ private static void setServiceNetworkForCallback(NsdServiceInfo info, int netId, int ifaceIdx) {
+ switch (netId) {
+ case NETID_UNSET:
+ info.setNetwork(null);
+ break;
+ case INetd.LOCAL_NET_ID:
+ // Special case for LOCAL_NET_ID: Networks on netId 99 are not generally
+ // visible / usable for apps, so do not return it. Store the interface
+ // index instead, so at least if the client tries to resolve the service
+ // with that NsdServiceInfo, it will be done on the same interface.
+ // If they recreate the NsdServiceInfo themselves, resolution would be
+ // done on all interfaces as before T, which should also work.
+ info.setNetwork(null);
+ info.setInterfaceIndex(ifaceIdx);
+ break;
+ default:
+ info.setNetwork(new Network(netId));
+ }
+ }
+
+ // The full service name is escaped from standard DNS rules on mdnsresponder, making it suitable
+ // for passing to standard system DNS APIs such as res_query() . Thus, make the service name
+ // unescape for getting right service address. See "Notes on DNS Name Escaping" on
+ // external/mdnsresponder/mDNSShared/dns_sd.h for more details.
+ private String unescape(String s) {
+ StringBuilder sb = new StringBuilder(s.length());
+ for (int i = 0; i < s.length(); ++i) {
+ char c = s.charAt(i);
+ if (c == '\\') {
+ if (++i >= s.length()) {
+ Log.e(TAG, "Unexpected end of escape sequence in: " + s);
+ break;
+ }
+ c = s.charAt(i);
+ if (c != '.' && c != '\\') {
+ if (i + 2 >= s.length()) {
+ Log.e(TAG, "Unexpected end of escape sequence in: " + s);
+ break;
+ }
+ c = (char) ((c - '0') * 100 + (s.charAt(i + 1) - '0') * 10
+ + (s.charAt(i + 2) - '0'));
+ i += 2;
+ }
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
@VisibleForTesting
NsdService(Context ctx, Handler handler, long cleanupDelayMs) {
mCleanupDelayMs = cleanupDelayMs;
@@ -738,9 +764,8 @@
String type = service.getServiceType();
int port = service.getPort();
byte[] textRecord = service.getTxtRecord();
- final Network network = service.getNetwork();
- final int registerInterface = getNetworkInterfaceIndex(network);
- if (network != null && registerInterface == IFACE_IDX_ANY) {
+ final int registerInterface = getNetworkInterfaceIndex(service);
+ if (service.getNetwork() != null && registerInterface == IFACE_IDX_ANY) {
Log.e(TAG, "Interface to register service on not found");
return false;
}
@@ -752,10 +777,9 @@
}
private boolean discoverServices(int discoveryId, NsdServiceInfo serviceInfo) {
- final Network network = serviceInfo.getNetwork();
final String type = serviceInfo.getServiceType();
- final int discoverInterface = getNetworkInterfaceIndex(network);
- if (network != null && discoverInterface == IFACE_IDX_ANY) {
+ final int discoverInterface = getNetworkInterfaceIndex(serviceInfo);
+ if (serviceInfo.getNetwork() != null && discoverInterface == IFACE_IDX_ANY) {
Log.e(TAG, "Interface to discover service on not found");
return false;
}
@@ -769,9 +793,8 @@
private boolean resolveService(int resolveId, NsdServiceInfo service) {
final String name = service.getServiceName();
final String type = service.getServiceType();
- final Network network = service.getNetwork();
- final int resolveInterface = getNetworkInterfaceIndex(network);
- if (network != null && resolveInterface == IFACE_IDX_ANY) {
+ final int resolveInterface = getNetworkInterfaceIndex(service);
+ if (service.getNetwork() != null && resolveInterface == IFACE_IDX_ANY) {
Log.e(TAG, "Interface to resolve service on not found");
return false;
}
@@ -787,8 +810,17 @@
* this is to support the legacy mdnsresponder implementation, which historically resolved
* services on an unspecified network.
*/
- private int getNetworkInterfaceIndex(Network network) {
- if (network == null) return IFACE_IDX_ANY;
+ private int getNetworkInterfaceIndex(NsdServiceInfo serviceInfo) {
+ final Network network = serviceInfo.getNetwork();
+ if (network == null) {
+ // Fallback to getInterfaceIndex if present (typically if the NsdServiceInfo was
+ // provided by NsdService from discovery results, and the service was found on an
+ // interface that has no app-usable Network).
+ if (serviceInfo.getInterfaceIndex() != 0) {
+ return serviceInfo.getInterfaceIndex();
+ }
+ return IFACE_IDX_ANY;
+ }
final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
if (cm == null) {
diff --git a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
index 6b623f4..6006539 100644
--- a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
+++ b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
@@ -16,23 +16,37 @@
package com.android.server.ethernet;
+import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
+
import android.annotation.Nullable;
+import android.content.ApexEnvironment;
import android.net.IpConfiguration;
import android.os.Environment;
import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.net.IpConfigStore;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
/**
* This class provides an API to store and manage Ethernet network configuration.
*/
public class EthernetConfigStore {
- private static final String ipConfigFile = Environment.getDataDirectory() +
- "/misc/ethernet/ipconfig.txt";
+ private static final String TAG = EthernetConfigStore.class.getSimpleName();
+ private static final String CONFIG_FILE = "ipconfig.txt";
+ private static final String FILE_PATH = "/misc/ethernet/";
+ private static final String LEGACY_IP_CONFIG_FILE_PATH = Environment.getDataDirectory()
+ + FILE_PATH;
+ private static final String APEX_IP_CONFIG_FILE_PATH = ApexEnvironment.getApexEnvironment(
+ TETHERING_MODULE_NAME).getDeviceProtectedDataDir() + FILE_PATH;
private IpConfigStore mStore = new IpConfigStore();
- private ArrayMap<String, IpConfiguration> mIpConfigurations;
+ private final ArrayMap<String, IpConfiguration> mIpConfigurations;
private IpConfiguration mIpConfigurationForDefaultInterface;
private final Object mSync = new Object();
@@ -40,22 +54,70 @@
mIpConfigurations = new ArrayMap<>(0);
}
- public void read() {
- synchronized (mSync) {
- ArrayMap<String, IpConfiguration> configs =
- IpConfigStore.readIpConfigurations(ipConfigFile);
+ private static boolean doesConfigFileExist(final String filepath) {
+ return new File(filepath).exists();
+ }
- // This configuration may exist in old file versions when there was only a single active
- // Ethernet interface.
- if (configs.containsKey("0")) {
- mIpConfigurationForDefaultInterface = configs.remove("0");
+ private void writeLegacyIpConfigToApexPath(final String newFilePath, final String oldFilePath,
+ final String filename) {
+ final File directory = new File(newFilePath);
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+
+ // Write the legacy IP config to the apex file path.
+ FileOutputStream fos = null;
+ final AtomicFile dst = new AtomicFile(new File(newFilePath + filename));
+ final AtomicFile src = new AtomicFile(new File(oldFilePath + filename));
+ try {
+ final byte[] raw = src.readFully();
+ if (raw.length > 0) {
+ fos = dst.startWrite();
+ fos.write(raw);
+ fos.flush();
+ dst.finishWrite(fos);
}
-
- mIpConfigurations = configs;
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to sync the legacy IP config to the apex file path.");
+ dst.failWrite(fos);
}
}
+ public void read() {
+ read(APEX_IP_CONFIG_FILE_PATH, LEGACY_IP_CONFIG_FILE_PATH, CONFIG_FILE);
+ }
+
+ @VisibleForTesting
+ void read(final String newFilePath, final String oldFilePath, final String filename) {
+ synchronized (mSync) {
+ // Attempt to read the IP configuration from apex file path first.
+ if (doesConfigFileExist(newFilePath + filename)) {
+ loadConfigFileLocked(newFilePath + filename);
+ return;
+ }
+
+ // If the config file doesn't exist in the apex file path, attempt to read it from
+ // the legacy file path, if config file exists, write the legacy IP configuration to
+ // apex config file path, this should just happen on the first boot. New or updated
+ // config entries are only written to the apex config file later.
+ if (!doesConfigFileExist(oldFilePath + filename)) return;
+ loadConfigFileLocked(oldFilePath + filename);
+ writeLegacyIpConfigToApexPath(newFilePath, oldFilePath, filename);
+ }
+ }
+
+ private void loadConfigFileLocked(final String filepath) {
+ final ArrayMap<String, IpConfiguration> configs =
+ IpConfigStore.readIpConfigurations(filepath);
+ mIpConfigurations.putAll(configs);
+ }
+
public void write(String iface, IpConfiguration config) {
+ write(iface, config, APEX_IP_CONFIG_FILE_PATH + CONFIG_FILE);
+ }
+
+ @VisibleForTesting
+ void write(String iface, IpConfiguration config, String filepath) {
boolean modified;
synchronized (mSync) {
@@ -67,7 +129,7 @@
}
if (modified) {
- mStore.writeIpConfigurations(ipConfigFile, mIpConfigurations);
+ mStore.writeIpConfigurations(filepath, mIpConfigurations);
}
}
}
@@ -80,9 +142,6 @@
@Nullable
public IpConfiguration getIpConfigurationForDefaultInterface() {
- synchronized (mSync) {
- return mIpConfigurationForDefaultInterface == null
- ? null : new IpConfiguration(mIpConfigurationForDefaultInterface);
- }
+ return null;
}
}
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index 09782fd..79802fb 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -31,10 +31,9 @@
import android.net.LinkProperties;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
import android.net.NetworkProvider;
import android.net.NetworkRequest;
-import android.net.NetworkSpecifier;
+import android.net.NetworkScore;
import android.net.ip.IIpClient;
import android.net.ip.IpClientCallbacks;
import android.net.ip.IpClientManager;
@@ -46,6 +45,7 @@
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
@@ -56,25 +56,23 @@
import java.io.FileDescriptor;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
- * {@link NetworkFactory} that represents Ethernet networks.
- *
- * This class reports a static network score of 70 when it is tracking an interface and that
- * interface's link is up, and a score of 0 otherwise.
+ * {@link NetworkProvider} that manages NetworkOffers for Ethernet networks.
*/
-public class EthernetNetworkFactory extends NetworkFactory {
+public class EthernetNetworkFactory {
private final static String TAG = EthernetNetworkFactory.class.getSimpleName();
final static boolean DBG = true;
- private final static int NETWORK_SCORE = 70;
private static final String NETWORK_TYPE = "Ethernet";
private final ConcurrentHashMap<String, NetworkInterfaceState> mTrackingInterfaces =
new ConcurrentHashMap<>();
private final Handler mHandler;
private final Context mContext;
+ private final NetworkProvider mProvider;
final Dependencies mDeps;
public static class Dependencies {
@@ -109,54 +107,24 @@
}
public EthernetNetworkFactory(Handler handler, Context context) {
- this(handler, context, new Dependencies());
+ this(handler, context, new NetworkProvider(context, handler.getLooper(), TAG),
+ new Dependencies());
}
@VisibleForTesting
- EthernetNetworkFactory(Handler handler, Context context, Dependencies deps) {
- super(handler.getLooper(), context, NETWORK_TYPE, createDefaultNetworkCapabilities());
-
+ EthernetNetworkFactory(Handler handler, Context context, NetworkProvider provider,
+ Dependencies deps) {
mHandler = handler;
mContext = context;
+ mProvider = provider;
mDeps = deps;
-
- setScoreFilter(NETWORK_SCORE);
}
- @Override
- public boolean acceptRequest(NetworkRequest request) {
- if (DBG) {
- Log.d(TAG, "acceptRequest, request: " + request);
- }
-
- return networkForRequest(request) != null;
- }
-
- @Override
- protected void needNetworkFor(NetworkRequest networkRequest) {
- NetworkInterfaceState network = networkForRequest(networkRequest);
-
- if (network == null) {
- Log.e(TAG, "needNetworkFor, failed to get a network for " + networkRequest);
- return;
- }
-
- if (++network.refCount == 1) {
- network.start();
- }
- }
-
- @Override
- protected void releaseNetworkFor(NetworkRequest networkRequest) {
- NetworkInterfaceState network = networkForRequest(networkRequest);
- if (network == null) {
- Log.e(TAG, "releaseNetworkFor, failed to get a network for " + networkRequest);
- return;
- }
-
- if (--network.refCount == 0) {
- network.stop();
- }
+ /**
+ * Registers the network provider with the system.
+ */
+ public void register() {
+ mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(mProvider);
}
/**
@@ -194,9 +162,8 @@
}
final NetworkInterfaceState iface = new NetworkInterfaceState(
- ifaceName, hwAddress, mHandler, mContext, ipConfig, nc, getProvider(), mDeps);
+ ifaceName, hwAddress, mHandler, mContext, ipConfig, nc, mProvider, mDeps);
mTrackingInterfaces.put(ifaceName, iface);
- updateCapabilityFilter();
}
@VisibleForTesting
@@ -237,7 +204,6 @@
final NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
iface.updateInterface(ipConfig, capabilities, listener);
mTrackingInterfaces.put(ifaceName, iface);
- updateCapabilityFilter();
}
private static NetworkCapabilities mixInCapabilities(NetworkCapabilities nc,
@@ -248,16 +214,6 @@
return builder.build();
}
- private void updateCapabilityFilter() {
- NetworkCapabilities capabilitiesFilter = createDefaultNetworkCapabilities();
- for (NetworkInterfaceState iface: mTrackingInterfaces.values()) {
- capabilitiesFilter = mixInCapabilities(capabilitiesFilter, iface.mCapabilities);
- }
-
- if (DBG) Log.d(TAG, "updateCapabilityFilter: " + capabilitiesFilter);
- setCapabilityFilter(capabilitiesFilter);
- }
-
private static NetworkCapabilities createDefaultNetworkCapabilities() {
return NetworkCapabilities.Builder
.withoutDefaultCapabilities()
@@ -268,11 +224,8 @@
protected void removeInterface(String interfaceName) {
NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
if (iface != null) {
- iface.maybeSendNetworkManagementCallbackForAbort();
- iface.stop();
+ iface.destroy();
}
-
- updateCapabilityFilter();
}
/** Returns true if state has been modified */
@@ -304,37 +257,6 @@
return mTrackingInterfaces.containsKey(ifaceName);
}
- private NetworkInterfaceState networkForRequest(NetworkRequest request) {
- String requestedIface = null;
-
- NetworkSpecifier specifier = request.getNetworkSpecifier();
- if (specifier instanceof EthernetNetworkSpecifier) {
- requestedIface = ((EthernetNetworkSpecifier) specifier)
- .getInterfaceName();
- }
-
- NetworkInterfaceState network = null;
- if (!TextUtils.isEmpty(requestedIface)) {
- NetworkInterfaceState n = mTrackingInterfaces.get(requestedIface);
- if (n != null && request.canBeSatisfiedBy(n.mCapabilities)) {
- network = n;
- }
- } else {
- for (NetworkInterfaceState n : mTrackingInterfaces.values()) {
- if (request.canBeSatisfiedBy(n.mCapabilities) && n.mLinkUp) {
- network = n;
- break;
- }
- }
- }
-
- if (DBG) {
- Log.i(TAG, "networkForRequest, request: " + request + ", network: " + network);
- }
-
- return network;
- }
-
private static void maybeSendNetworkManagementCallback(
@Nullable final INetworkInterfaceOutcomeReceiver listener,
@Nullable final String iface,
@@ -363,12 +285,14 @@
private final Context mContext;
private final NetworkProvider mNetworkProvider;
private final Dependencies mDeps;
+ private final NetworkProvider.NetworkOfferCallback mNetworkOfferCallback;
private static String sTcpBufferSizes = null; // Lazy initialized.
private boolean mLinkUp;
private int mLegacyType;
private LinkProperties mLinkProperties = new LinkProperties();
+ private Set<NetworkRequest> mRequests = new ArraySet<>();
private volatile @Nullable IpClientManager mIpClient;
private @NonNull NetworkCapabilities mCapabilities;
@@ -397,8 +321,6 @@
ConnectivityManager.TYPE_NONE);
}
- long refCount = 0;
-
private class EthernetIpClientCallback extends IpClientCallbacks {
private final ConditionVariable mIpClientStartCv = new ConditionVariable(false);
private final ConditionVariable mIpClientShutdownCv = new ConditionVariable(false);
@@ -469,6 +391,35 @@
}
}
+ private class EthernetNetworkOfferCallback implements NetworkProvider.NetworkOfferCallback {
+ @Override
+ public void onNetworkNeeded(@NonNull NetworkRequest request) {
+ if (DBG) {
+ Log.d(TAG, String.format("%s: onNetworkNeeded for request: %s", name, request));
+ }
+ // When the network offer is first registered, onNetworkNeeded is called with all
+ // existing requests.
+ // ConnectivityService filters requests for us based on the NetworkCapabilities
+ // passed in the registerNetworkOffer() call.
+ mRequests.add(request);
+ // if the network is already started, this is a no-op.
+ start();
+ }
+
+ @Override
+ public void onNetworkUnneeded(@NonNull NetworkRequest request) {
+ if (DBG) {
+ Log.d(TAG,
+ String.format("%s: onNetworkUnneeded for request: %s", name, request));
+ }
+ mRequests.remove(request);
+ if (mRequests.isEmpty()) {
+ // not currently serving any requests, stop the network.
+ stop();
+ }
+ }
+ }
+
NetworkInterfaceState(String ifaceName, String hwAddress, Handler handler, Context context,
@NonNull IpConfiguration ipConfig, @NonNull NetworkCapabilities capabilities,
NetworkProvider networkProvider, Dependencies deps) {
@@ -480,6 +431,7 @@
mContext = context;
mNetworkProvider = networkProvider;
mDeps = deps;
+ mNetworkOfferCallback = new EthernetNetworkOfferCallback();
mHwAddress = hwAddress;
}
@@ -502,9 +454,21 @@
+ "transport type.");
}
+ private static NetworkScore getBestNetworkScore() {
+ return new NetworkScore.Builder().build();
+ }
+
private void setCapabilities(@NonNull final NetworkCapabilities capabilities) {
mCapabilities = new NetworkCapabilities(capabilities);
mLegacyType = getLegacyType(mCapabilities);
+
+ 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);
+ }
}
void updateInterface(@Nullable final IpConfiguration ipConfig,
@@ -666,20 +630,21 @@
mLinkUp = up;
if (!up) { // was up, goes down
- // Send an abort on a provisioning request callback if necessary before stopping.
- maybeSendNetworkManagementCallbackForAbort();
- stop();
+ // retract network offer and stop IpClient.
+ destroy();
// If only setting the interface down, send a callback to signal completion.
EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, name, null);
} else { // was down, goes up
- stop();
- start(listener);
+ // register network offer
+ mNetworkProvider.registerNetworkOffer(getBestNetworkScore(),
+ new NetworkCapabilities(mCapabilities), (cmd) -> mHandler.post(cmd),
+ mNetworkOfferCallback);
}
return true;
}
- void stop() {
+ private void stop() {
// Invalidate all previous start requests
if (mIpClient != null) {
mIpClient.shutdown();
@@ -695,6 +660,13 @@
mLinkProperties.clear();
}
+ public void destroy() {
+ mNetworkProvider.unregisterNetworkOffer(mNetworkOfferCallback);
+ maybeSendNetworkManagementCallbackForAbort();
+ stop();
+ mRequests.clear();
+ }
+
private static void provisionIpClient(@NonNull final IpClientManager ipClient,
@NonNull final IpConfiguration config, @NonNull final String tcpBufferSizes) {
if (config.getProxySettings() == ProxySettings.STATIC ||
@@ -734,7 +706,6 @@
@Override
public String toString() {
return getClass().getSimpleName() + "{ "
- + "refCount: " + refCount + ", "
+ "iface: " + name + ", "
+ "up: " + mLinkUp + ", "
+ "hwAddress: " + mHwAddress + ", "
@@ -747,7 +718,6 @@
}
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
- super.dump(fd, pw, args);
pw.println(getClass().getSimpleName());
pw.println("Tracking interfaces:");
pw.increaseIndent();
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
index 5e830ad..71d3e4f 100644
--- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -22,11 +22,11 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.net.EthernetNetworkUpdateRequest;
import android.net.IEthernetManager;
import android.net.IEthernetServiceListener;
import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.ITetheredInterfaceCallback;
-import android.net.EthernetNetworkUpdateRequest;
import android.net.IpConfiguration;
import android.net.NetworkCapabilities;
import android.os.Binder;
@@ -260,27 +260,27 @@
}
@Override
- public void connectNetwork(@NonNull final String iface,
+ public void enableInterface(@NonNull final String iface,
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
- Log.i(TAG, "connectNetwork called with: iface=" + iface + ", listener=" + listener);
+ Log.i(TAG, "enableInterface called with: iface=" + iface + ", listener=" + listener);
Objects.requireNonNull(iface);
throwIfEthernetNotStarted();
- enforceAdminPermission(iface, true, "connectNetwork()");
+ enforceAdminPermission(iface, false, "enableInterface()");
- mTracker.connectNetwork(iface, listener);
+ mTracker.enableInterface(iface, listener);
}
@Override
- public void disconnectNetwork(@NonNull final String iface,
+ public void disableInterface(@NonNull final String iface,
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
- Log.i(TAG, "disconnectNetwork called with: iface=" + iface + ", listener=" + listener);
+ Log.i(TAG, "disableInterface called with: iface=" + iface + ", listener=" + listener);
Objects.requireNonNull(iface);
throwIfEthernetNotStarted();
- enforceAdminPermission(iface, true, "connectNetwork()");
+ enforceAdminPermission(iface, false, "disableInterface()");
- mTracker.disconnectNetwork(iface, listener);
+ mTracker.disableInterface(iface, listener);
}
@Override
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index e9053dd..6405795 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -228,7 +228,7 @@
*/
protected void broadcastInterfaceStateChange(@NonNull String iface) {
ensureRunningOnEthernetServiceThread();
- final int state = mFactory.getInterfaceState(iface);
+ final int state = getInterfaceState(iface);
final int role = getInterfaceRole(iface);
final IpConfiguration config = getIpConfigurationForCallback(iface, state);
final int n = mListeners.beginBroadcast();
@@ -285,13 +285,13 @@
}
@VisibleForTesting(visibility = PACKAGE)
- protected void connectNetwork(@NonNull final String iface,
+ protected void enableInterface(@NonNull final String iface,
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
mHandler.post(() -> updateInterfaceState(iface, true, listener));
}
@VisibleForTesting(visibility = PACKAGE)
- protected void disconnectNetwork(@NonNull final String iface,
+ protected void disableInterface(@NonNull final String iface,
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
mHandler.post(() -> updateInterfaceState(iface, false, listener));
}
@@ -435,15 +435,34 @@
if (mDefaultInterface != null) {
removeInterface(mDefaultInterface);
addInterface(mDefaultInterface);
+ // when this broadcast is sent, any calls to notifyTetheredInterfaceAvailable or
+ // notifyTetheredInterfaceUnavailable have already happened
+ broadcastInterfaceStateChange(mDefaultInterface);
}
}
+ private int getInterfaceState(final String iface) {
+ if (mFactory.hasInterface(iface)) {
+ return mFactory.getInterfaceState(iface);
+ }
+ if (getInterfaceMode(iface) == INTERFACE_MODE_SERVER) {
+ // server mode interfaces are not tracked by the factory.
+ // TODO(b/234743836): interface state for server mode interfaces is not tracked
+ // properly; just return link up.
+ return EthernetManager.STATE_LINK_UP;
+ }
+ return EthernetManager.STATE_ABSENT;
+ }
+
private int getInterfaceRole(final String iface) {
- if (!mFactory.hasInterface(iface)) return EthernetManager.ROLE_NONE;
- final int mode = getInterfaceMode(iface);
- return (mode == INTERFACE_MODE_CLIENT)
- ? EthernetManager.ROLE_CLIENT
- : EthernetManager.ROLE_SERVER;
+ if (mFactory.hasInterface(iface)) {
+ // only client mode interfaces are tracked by the factory.
+ return EthernetManager.ROLE_CLIENT;
+ }
+ if (getInterfaceMode(iface) == INTERFACE_MODE_SERVER) {
+ return EthernetManager.ROLE_SERVER;
+ }
+ return EthernetManager.ROLE_NONE;
}
private int getInterfaceMode(final String iface) {
@@ -586,14 +605,18 @@
}
}
- private class InterfaceObserver extends BaseNetdUnsolicitedEventListener {
+ @VisibleForTesting
+ class InterfaceObserver extends BaseNetdUnsolicitedEventListener {
@Override
public void onInterfaceLinkStateChanged(String iface, boolean up) {
if (DBG) {
Log.i(TAG, "interfaceLinkStateChanged, iface: " + iface + ", up: " + up);
}
- mHandler.post(() -> updateInterfaceState(iface, up));
+ mHandler.post(() -> {
+ if (mEthernetState == ETHERNET_STATE_DISABLED) return;
+ updateInterfaceState(iface, up);
+ });
}
@Override
@@ -601,7 +624,10 @@
if (DBG) {
Log.i(TAG, "onInterfaceAdded, iface: " + iface);
}
- mHandler.post(() -> maybeTrackInterface(iface));
+ mHandler.post(() -> {
+ if (mEthernetState == ETHERNET_STATE_DISABLED) return;
+ maybeTrackInterface(iface);
+ });
}
@Override
@@ -609,7 +635,10 @@
if (DBG) {
Log.i(TAG, "onInterfaceRemoved, iface: " + iface);
}
- mHandler.post(() -> stopTrackingInterface(iface));
+ mHandler.post(() -> {
+ if (mEthernetState == ETHERNET_STATE_DISABLED) return;
+ stopTrackingInterface(iface);
+ });
}
}
@@ -888,6 +917,8 @@
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
postAndWaitForRunnable(() -> {
pw.println(getClass().getSimpleName());
+ pw.println("Ethernet State: "
+ + (mEthernetState == ETHERNET_STATE_ENABLED ? "enabled" : "disabled"));
pw.println("Ethernet interface name filter: " + mIfaceMatch);
pw.println("Default interface: " + mDefaultInterface);
pw.println("Default interface mode: " + mDefaultInterfaceMode);
diff --git a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
index 5011dec..3b44d81 100644
--- a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
+++ b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
@@ -38,7 +38,7 @@
private static final String TAG = BpfInterfaceMapUpdater.class.getSimpleName();
// This is current path but may be changed soon.
private static final String IFACE_INDEX_NAME_MAP_PATH =
- "/sys/fs/bpf/net_shared/map_netd_iface_index_name_map";
+ "/sys/fs/bpf/netd_shared/map_netd_iface_index_name_map";
private final IBpfMap<U32, InterfaceMapValue> mBpfMap;
private final INetd mNetd;
private final Handler mHandler;
diff --git a/service-t/src/com/android/server/net/NetworkStatsObservers.java b/service-t/src/com/android/server/net/NetworkStatsObservers.java
index df4e7f5..1cd670a 100644
--- a/service-t/src/com/android/server/net/NetworkStatsObservers.java
+++ b/service-t/src/com/android/server/net/NetworkStatsObservers.java
@@ -198,7 +198,7 @@
if (LOG) Log.d(TAG, "Unregistering " + requestInfo);
mDataUsageRequests.remove(request.requestId);
- mDataUsageRequestsPerUid.decrementCountOrThrow(callingUid);
+ mDataUsageRequestsPerUid.decrementCountOrThrow(requestInfo.mCallingUid);
requestInfo.unlinkDeathRecipient();
requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED);
}
diff --git a/service-t/src/com/android/server/net/NetworkStatsRecorder.java b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
index 6f070d7..d99e164 100644
--- a/service-t/src/com/android/server/net/NetworkStatsRecorder.java
+++ b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
@@ -21,6 +21,7 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+import android.annotation.NonNull;
import android.net.NetworkIdentitySet;
import android.net.NetworkStats;
import android.net.NetworkStats.NonMonotonicObserver;
@@ -42,7 +43,6 @@
import libcore.io.IoUtils;
import java.io.ByteArrayOutputStream;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -68,7 +68,7 @@
private static final String TAG_NETSTATS_DUMP = "netstats_dump";
- /** Dump before deleting in {@link #recoverFromWtf()}. */
+ /** Dump before deleting in {@link #recoverAndDeleteData()}. */
private static final boolean DUMP_BEFORE_DELETE = true;
private final FileRotator mRotator;
@@ -156,6 +156,15 @@
return mSinceBoot;
}
+ public long getBucketDuration() {
+ return mBucketDuration;
+ }
+
+ @NonNull
+ public String getCookie() {
+ return mCookie;
+ }
+
/**
* Load complete history represented by {@link FileRotator}. Caches
* internally as a {@link WeakReference}, and updated with future
@@ -189,10 +198,10 @@
res.recordCollection(mPending);
} catch (IOException e) {
Log.wtf(TAG, "problem completely reading network stats", e);
- recoverFromWtf();
+ recoverAndDeleteData();
} catch (OutOfMemoryError e) {
Log.wtf(TAG, "problem completely reading network stats", e);
- recoverFromWtf();
+ recoverAndDeleteData();
}
return res;
}
@@ -300,10 +309,10 @@
mPending.reset();
} catch (IOException e) {
Log.wtf(TAG, "problem persisting pending stats", e);
- recoverFromWtf();
+ recoverAndDeleteData();
} catch (OutOfMemoryError e) {
Log.wtf(TAG, "problem persisting pending stats", e);
- recoverFromWtf();
+ recoverAndDeleteData();
}
}
}
@@ -319,10 +328,10 @@
mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
} catch (IOException e) {
Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
- recoverFromWtf();
+ recoverAndDeleteData();
} catch (OutOfMemoryError e) {
Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
- recoverFromWtf();
+ recoverAndDeleteData();
}
}
@@ -347,8 +356,7 @@
/**
* Rewriter that will combine current {@link NetworkStatsCollection} values
- * with anything read from disk, and write combined set to disk. Clears the
- * original {@link NetworkStatsCollection} when finished writing.
+ * with anything read from disk, and write combined set to disk.
*/
private static class CombiningRewriter implements FileRotator.Rewriter {
private final NetworkStatsCollection mCollection;
@@ -375,7 +383,6 @@
@Override
public void write(OutputStream out) throws IOException {
mCollection.write(out);
- mCollection.reset();
}
}
@@ -415,43 +422,20 @@
}
}
- public void importLegacyNetworkLocked(File file) throws IOException {
- Objects.requireNonNull(mRotator, "missing FileRotator");
-
- // legacy file still exists; start empty to avoid double importing
- mRotator.deleteAll();
-
- final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
- collection.readLegacyNetwork(file);
-
- final long startMillis = collection.getStartMillis();
- final long endMillis = collection.getEndMillis();
-
- if (!collection.isEmpty()) {
- // process legacy data, creating active file at starting time, then
- // using end time to possibly trigger rotation.
- mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
- mRotator.maybeRotate(endMillis);
+ /**
+ * Import a specified {@link NetworkStatsCollection} instance into this recorder,
+ * and write it into a standalone file.
+ * @param collection The target {@link NetworkStatsCollection} instance to be imported.
+ */
+ public void importCollectionLocked(@NonNull NetworkStatsCollection collection)
+ throws IOException {
+ if (mRotator != null) {
+ mRotator.rewriteSingle(new CombiningRewriter(collection), collection.getStartMillis(),
+ collection.getEndMillis());
}
- }
- public void importLegacyUidLocked(File file) throws IOException {
- Objects.requireNonNull(mRotator, "missing FileRotator");
-
- // legacy file still exists; start empty to avoid double importing
- mRotator.deleteAll();
-
- final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
- collection.readLegacyUid(file, mOnlyTags);
-
- final long startMillis = collection.getStartMillis();
- final long endMillis = collection.getEndMillis();
-
- if (!collection.isEmpty()) {
- // process legacy data, creating active file at starting time, then
- // using end time to possibly trigger rotation.
- mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
- mRotator.maybeRotate(endMillis);
+ if (mComplete != null) {
+ throw new IllegalStateException("cannot import data when data already loaded");
}
}
@@ -501,10 +485,10 @@
mBucketDuration, cutoffMillis));
} catch (IOException e) {
Log.wtf(TAG, "problem importing netstats", e);
- recoverFromWtf();
+ recoverAndDeleteData();
} catch (OutOfMemoryError e) {
Log.wtf(TAG, "problem importing netstats", e);
- recoverFromWtf();
+ recoverAndDeleteData();
}
}
@@ -555,7 +539,7 @@
* Recover from {@link FileRotator} failure by dumping state to
* {@link DropBoxManager} and deleting contents.
*/
- private void recoverFromWtf() {
+ void recoverAndDeleteData() {
if (DUMP_BEFORE_DELETE) {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index b2d8b5e..cd29185 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -67,6 +67,7 @@
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.usage.NetworkStatsManager;
+import android.content.ApexEnvironment;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -75,6 +76,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
+import android.net.ConnectivityManager;
import android.net.DataUsageRequest;
import android.net.INetd;
import android.net.INetworkStatsService;
@@ -100,6 +102,7 @@
import android.net.UnderlyingNetworkInfo;
import android.net.Uri;
import android.net.netstats.IUsageCallback;
+import android.net.netstats.NetworkStatsDataMigrationUtils;
import android.net.netstats.provider.INetworkStatsProvider;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.netstats.provider.NetworkStatsProvider;
@@ -118,6 +121,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.NetworkInterfaceProto;
@@ -143,6 +147,7 @@
import com.android.net.module.util.BinderUtils;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.LocationPermissionChecker;
import com.android.net.module.util.NetworkStatsUtils;
@@ -155,7 +160,9 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.nio.file.Path;
import java.time.Clock;
+import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
@@ -222,15 +229,31 @@
"netstats_combine_subtype_enabled";
private static final String UID_COUNTERSET_MAP_PATH =
- "/sys/fs/bpf/net_shared/map_netd_uid_counterset_map";
+ "/sys/fs/bpf/netd_shared/map_netd_uid_counterset_map";
private static final String COOKIE_TAG_MAP_PATH =
- "/sys/fs/bpf/net_shared/map_netd_cookie_tag_map";
+ "/sys/fs/bpf/netd_shared/map_netd_cookie_tag_map";
private static final String APP_UID_STATS_MAP_PATH =
- "/sys/fs/bpf/net_shared/map_netd_app_uid_stats_map";
+ "/sys/fs/bpf/netd_shared/map_netd_app_uid_stats_map";
private static final String STATS_MAP_A_PATH =
- "/sys/fs/bpf/net_shared/map_netd_stats_map_A";
+ "/sys/fs/bpf/netd_shared/map_netd_stats_map_A";
private static final String STATS_MAP_B_PATH =
- "/sys/fs/bpf/net_shared/map_netd_stats_map_B";
+ "/sys/fs/bpf/netd_shared/map_netd_stats_map_B";
+
+ /**
+ * DeviceConfig flag used to indicate whether the files should be stored in the apex data
+ * directory.
+ */
+ static final String NETSTATS_STORE_FILES_IN_APEXDATA = "netstats_store_files_in_apexdata";
+ /**
+ * DeviceConfig flag is used to indicate whether the legacy files need to be imported, and
+ * retry count before giving up. Only valid when {@link #NETSTATS_STORE_FILES_IN_APEXDATA}
+ * set to true. Note that the value gets rollback when the mainline module gets rollback.
+ */
+ static final String NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS =
+ "netstats_import_legacy_target_attempts";
+ static final int DEFAULT_NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS = 1;
+ static final String NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME = "import.attempts";
+ static final String NETSTATS_IMPORT_SUCCESS_COUNTER_NAME = "import.successes";
private final Context mContext;
private final NetworkStatsFactory mStatsFactory;
@@ -239,8 +262,7 @@
private final NetworkStatsSettings mSettings;
private final NetworkStatsObservers mStatsObservers;
- private final File mSystemDir;
- private final File mBaseDir;
+ private final File mStatsDir;
private final PowerManager.WakeLock mWakeLock;
@@ -250,6 +272,12 @@
protected INetd mNetd;
private final AlertObserver mAlertObserver = new AlertObserver();
+ // Persistent counters that backed by AtomicFile which stored in the data directory as a file,
+ // to track attempts/successes count across reboot. Note that these counter values will be
+ // rollback as the module rollbacks.
+ private PersistentInt mImportLegacyAttemptsCounter = null;
+ private PersistentInt mImportLegacySuccessesCounter = null;
+
@VisibleForTesting
public static final String ACTION_NETWORK_STATS_POLL =
"com.android.server.action.NETWORK_STATS_POLL";
@@ -405,16 +433,6 @@
@NonNull
private final BpfInterfaceMapUpdater mInterfaceMapUpdater;
- private static @NonNull File getDefaultSystemDir() {
- return new File(Environment.getDataDirectory(), "system");
- }
-
- private static @NonNull File getDefaultBaseDir() {
- File baseDir = new File(getDefaultSystemDir(), "netstats");
- baseDir.mkdirs();
- return baseDir;
- }
-
private static @NonNull Clock getDefaultClock() {
return new BestClock(ZoneOffset.UTC, SystemClock.currentNetworkTimeClock(),
Clock.systemUTC());
@@ -506,8 +524,7 @@
INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
alarmManager, wakeLock, getDefaultClock(),
new DefaultNetworkStatsSettings(), new NetworkStatsFactory(context),
- new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(),
- new Dependencies());
+ new NetworkStatsObservers(), new Dependencies());
return service;
}
@@ -517,8 +534,8 @@
@VisibleForTesting
NetworkStatsService(Context context, INetd netd, AlarmManager alarmManager,
PowerManager.WakeLock wakeLock, Clock clock, NetworkStatsSettings settings,
- NetworkStatsFactory factory, NetworkStatsObservers statsObservers, File systemDir,
- File baseDir, @NonNull Dependencies deps) {
+ NetworkStatsFactory factory, NetworkStatsObservers statsObservers,
+ @NonNull Dependencies deps) {
mContext = Objects.requireNonNull(context, "missing Context");
mNetd = Objects.requireNonNull(netd, "missing Netd");
mAlarmManager = Objects.requireNonNull(alarmManager, "missing AlarmManager");
@@ -527,9 +544,11 @@
mWakeLock = Objects.requireNonNull(wakeLock, "missing WakeLock");
mStatsFactory = Objects.requireNonNull(factory, "missing factory");
mStatsObservers = Objects.requireNonNull(statsObservers, "missing NetworkStatsObservers");
- mSystemDir = Objects.requireNonNull(systemDir, "missing systemDir");
- mBaseDir = Objects.requireNonNull(baseDir, "missing baseDir");
mDeps = Objects.requireNonNull(deps, "missing Dependencies");
+ mStatsDir = mDeps.getOrCreateStatsDir();
+ if (!mStatsDir.exists()) {
+ throw new IllegalStateException("Persist data directory does not exist: " + mStatsDir);
+ }
final HandlerThread handlerThread = mDeps.makeHandlerThread();
handlerThread.start();
@@ -556,6 +575,87 @@
@VisibleForTesting
public static class Dependencies {
/**
+ * Get legacy platform stats directory.
+ */
+ @NonNull
+ public File getLegacyStatsDir() {
+ final File systemDataDir = new File(Environment.getDataDirectory(), "system");
+ return new File(systemDataDir, "netstats");
+ }
+
+ /**
+ * Get or create the directory that stores the persisted data usage.
+ */
+ @NonNull
+ public File getOrCreateStatsDir() {
+ final boolean storeInApexDataDir = getStoreFilesInApexData();
+
+ final File statsDataDir;
+ if (storeInApexDataDir) {
+ final File apexDataDir = ApexEnvironment
+ .getApexEnvironment(DeviceConfigUtils.TETHERING_MODULE_NAME)
+ .getDeviceProtectedDataDir();
+ statsDataDir = new File(apexDataDir, "netstats");
+
+ } else {
+ statsDataDir = getLegacyStatsDir();
+ }
+
+ if (statsDataDir.exists() || statsDataDir.mkdirs()) {
+ return statsDataDir;
+ }
+ throw new IllegalStateException("Cannot write into stats data directory: "
+ + statsDataDir);
+ }
+
+ /**
+ * Get the count of import legacy target attempts.
+ */
+ public int getImportLegacyTargetAttempts() {
+ return DeviceConfigUtils.getDeviceConfigPropertyInt(
+ DeviceConfig.NAMESPACE_TETHERING,
+ NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS,
+ DEFAULT_NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS);
+ }
+
+ /**
+ * Create the persistent counter that counts total import legacy stats attempts.
+ */
+ public PersistentInt createImportLegacyAttemptsCounter(@NonNull Path path)
+ throws IOException {
+ // TODO: Modify PersistentInt to call setStartTime every time a write is made.
+ // Create and pass a real logger here.
+ return new PersistentInt(path.toString(), null /* logger */);
+ }
+
+ /**
+ * Create the persistent counter that counts total import legacy stats successes.
+ */
+ public PersistentInt createImportLegacySuccessesCounter(@NonNull Path path)
+ throws IOException {
+ return new PersistentInt(path.toString(), null /* logger */);
+ }
+
+ /**
+ * Get the flag of storing files in the apex data directory.
+ * @return whether to store files in the apex data directory.
+ */
+ public boolean getStoreFilesInApexData() {
+ return DeviceConfigUtils.getDeviceConfigPropertyBoolean(
+ DeviceConfig.NAMESPACE_TETHERING,
+ NETSTATS_STORE_FILES_IN_APEXDATA, true);
+ }
+
+ /**
+ * Read legacy persisted network stats from disk.
+ */
+ @NonNull
+ public NetworkStatsCollection readPlatformCollection(
+ @NonNull String prefix, long bucketDuration) throws IOException {
+ return NetworkStatsDataMigrationUtils.readPlatformCollection(prefix, bucketDuration);
+ }
+
+ /**
* Create a HandlerThread to use in NetworkStatsService.
*/
@NonNull
@@ -690,14 +790,15 @@
mSystemReady = true;
// create data recorders along with historical rotators
- mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false);
- mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false);
- mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false);
- mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true);
+ mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, mStatsDir);
+ mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, mStatsDir);
+ mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, mStatsDir);
+ mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true,
+ mStatsDir);
updatePersistThresholdsLocked();
- // upgrade any legacy stats, migrating them to rotated files
+ // upgrade any legacy stats
maybeUpgradeLegacyStatsLocked();
// read historical network stats from disk, since policy service
@@ -757,11 +858,12 @@
}
private NetworkStatsRecorder buildRecorder(
- String prefix, NetworkStatsSettings.Config config, boolean includeTags) {
+ String prefix, NetworkStatsSettings.Config config, boolean includeTags,
+ File baseDir) {
final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService(
Context.DROPBOX_SERVICE);
return new NetworkStatsRecorder(new FileRotator(
- mBaseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
+ baseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
mNonMonotonicObserver, dropBox, prefix, config.bucketDuration, includeTags);
}
@@ -791,32 +893,298 @@
mSystemReady = false;
}
+ private static class MigrationInfo {
+ public final NetworkStatsRecorder recorder;
+ public NetworkStatsCollection collection;
+ public boolean imported;
+ MigrationInfo(@NonNull final NetworkStatsRecorder recorder) {
+ this.recorder = recorder;
+ collection = null;
+ imported = false;
+ }
+ }
+
@GuardedBy("mStatsLock")
private void maybeUpgradeLegacyStatsLocked() {
- File file;
- try {
- file = new File(mSystemDir, "netstats.bin");
- if (file.exists()) {
- mDevRecorder.importLegacyNetworkLocked(file);
- file.delete();
- }
-
- file = new File(mSystemDir, "netstats_xt.bin");
- if (file.exists()) {
- file.delete();
- }
-
- file = new File(mSystemDir, "netstats_uid.bin");
- if (file.exists()) {
- mUidRecorder.importLegacyUidLocked(file);
- mUidTagRecorder.importLegacyUidLocked(file);
- file.delete();
- }
- } catch (IOException e) {
- Log.wtf(TAG, "problem during legacy upgrade", e);
- } catch (OutOfMemoryError e) {
- Log.wtf(TAG, "problem during legacy upgrade", e);
+ final boolean storeFilesInApexData = mDeps.getStoreFilesInApexData();
+ if (!storeFilesInApexData) {
+ return;
}
+ try {
+ mImportLegacyAttemptsCounter = mDeps.createImportLegacyAttemptsCounter(
+ mStatsDir.toPath().resolve(NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME));
+ mImportLegacySuccessesCounter = mDeps.createImportLegacySuccessesCounter(
+ mStatsDir.toPath().resolve(NETSTATS_IMPORT_SUCCESS_COUNTER_NAME));
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to create persistent counters, skip.", e);
+ return;
+ }
+
+ final int targetAttempts = mDeps.getImportLegacyTargetAttempts();
+ final int attempts;
+ try {
+ attempts = mImportLegacyAttemptsCounter.get();
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to read attempts counter, skip.", e);
+ return;
+ }
+ if (attempts >= targetAttempts) return;
+
+ Log.i(TAG, "Starting import : attempts " + attempts + "/" + targetAttempts);
+
+ final MigrationInfo[] migrations = new MigrationInfo[]{
+ new MigrationInfo(mDevRecorder), new MigrationInfo(mXtRecorder),
+ new MigrationInfo(mUidRecorder), new MigrationInfo(mUidTagRecorder)
+ };
+
+ // Legacy directories will be created by recorders if they do not exist
+ final File legacyBaseDir = mDeps.getLegacyStatsDir();
+ final NetworkStatsRecorder[] legacyRecorders = new NetworkStatsRecorder[]{
+ buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, legacyBaseDir),
+ buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, legacyBaseDir),
+ buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, legacyBaseDir),
+ buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true, legacyBaseDir)
+ };
+
+ long migrationEndTime = Long.MIN_VALUE;
+ boolean endedWithFallback = false;
+ try {
+ // First, read all legacy collections. This is OEM code and it can throw. Don't
+ // commit any data to disk until all are read.
+ for (int i = 0; i < migrations.length; i++) {
+ String errMsg = null;
+ Throwable exception = null;
+ final MigrationInfo migration = migrations[i];
+
+ // Read the collection from platform code, and using fallback method if throws.
+ try {
+ migration.collection = readPlatformCollectionForRecorder(migration.recorder);
+ } catch (Throwable e) {
+ errMsg = "Failed to read stats from platform";
+ exception = e;
+ }
+
+ // Also read the collection with legacy method
+ final NetworkStatsRecorder legacyRecorder = legacyRecorders[i];
+
+ final NetworkStatsCollection legacyStats;
+ try {
+ legacyStats = legacyRecorder.getOrLoadCompleteLocked();
+ } catch (Throwable e) {
+ Log.wtf(TAG, "Failed to read stats with legacy method for recorder " + i, e);
+ if (exception != null) {
+ throw exception;
+ } else {
+ // Use newer stats, since that's all that is available
+ continue;
+ }
+ }
+
+ if (errMsg == null) {
+ try {
+ errMsg = compareStats(migration.collection, legacyStats);
+ } catch (Throwable e) {
+ errMsg = "Failed to compare migrated stats with all stats";
+ exception = e;
+ }
+ }
+
+ if (errMsg != null) {
+ Log.wtf(TAG, "NetworkStats import for migration " + i
+ + " returned invalid data: " + errMsg, exception);
+ // Fall back to legacy stats for this boot. The stats for old data will be
+ // re-imported again on next boot until they succeed the import. This is fine
+ // since every import clears the previous stats for the imported timespan.
+ migration.collection = legacyStats;
+ endedWithFallback = true;
+ }
+ }
+
+ // Find the latest end time.
+ for (final MigrationInfo migration : migrations) {
+ final long migrationEnd = migration.collection.getEndMillis();
+ if (migrationEnd > migrationEndTime) migrationEndTime = migrationEnd;
+ }
+
+ // Reading all collections from legacy data has succeeded. At this point it is
+ // safe to start overwriting the files on disk. The next step is to remove all
+ // data in the new location that overlaps with imported data. This ensures that
+ // any data in the new location that was created by a previous failed import is
+ // ignored. After that, write the imported data into the recorder. The code
+ // below can still possibly throw (disk error or OutOfMemory for example), but
+ // does not depend on code from non-mainline code.
+ Log.i(TAG, "Rewriting data with imported collections with cutoff "
+ + Instant.ofEpochMilli(migrationEndTime));
+ for (final MigrationInfo migration : migrations) {
+ migration.imported = true;
+ migration.recorder.removeDataBefore(migrationEndTime);
+ if (migration.collection.isEmpty()) continue;
+ migration.recorder.importCollectionLocked(migration.collection);
+ }
+
+ if (endedWithFallback) {
+ Log.wtf(TAG, "Imported platform collections with legacy fallback");
+ } else {
+ Log.i(TAG, "Successfully imported platform collections");
+ }
+ } catch (Throwable e) {
+ // The code above calls OEM code that may behave differently across devices.
+ // It can throw any exception including RuntimeExceptions and
+ // OutOfMemoryErrors. Try to recover anyway.
+ Log.wtf(TAG, "Platform data import failed. Remaining tries "
+ + (targetAttempts - attempts), e);
+
+ // Failed this time around : try again next time unless we're out of tries.
+ try {
+ mImportLegacyAttemptsCounter.set(attempts + 1);
+ } catch (IOException ex) {
+ Log.wtf(TAG, "Failed to update attempts counter.", ex);
+ }
+
+ // Try to remove any data from the failed import.
+ if (migrationEndTime > Long.MIN_VALUE) {
+ try {
+ for (final MigrationInfo migration : migrations) {
+ if (migration.imported) {
+ migration.recorder.removeDataBefore(migrationEndTime);
+ }
+ }
+ } catch (Throwable f) {
+ // If rollback still throws, there isn't much left to do. Try nuking
+ // all data, since that's the last stop. If nuking still throws, the
+ // framework will reboot, and if there are remaining tries, the migration
+ // process will retry, which is fine because it's idempotent.
+ for (final MigrationInfo migration : migrations) {
+ migration.recorder.recoverAndDeleteData();
+ }
+ }
+ }
+
+ return;
+ }
+
+ // Success ! No need to import again next time.
+ try {
+ mImportLegacyAttemptsCounter.set(targetAttempts);
+ // The successes counter is only for debugging. Hence, the synchronization
+ // between these two counters are not very critical.
+ final int successCount = mImportLegacySuccessesCounter.get();
+ mImportLegacySuccessesCounter.set(successCount + 1);
+ } catch (IOException e) {
+ Log.wtf(TAG, "Succeed but failed to update counters.", e);
+ }
+ }
+
+ private static String str(NetworkStatsCollection.Key key) {
+ StringBuilder sb = new StringBuilder()
+ .append(key.ident.toString())
+ .append(" uid=").append(key.uid);
+ if (key.set != SET_FOREGROUND) {
+ sb.append(" set=").append(key.set);
+ }
+ if (key.tag != 0) {
+ sb.append(" tag=").append(key.tag);
+ }
+ return sb.toString();
+ }
+
+ // The importer will modify some keys when importing them.
+ // In order to keep the comparison code simple, add such special cases here and simply
+ // ignore them. This should not impact fidelity much because the start/end checks and the total
+ // bytes check still need to pass.
+ private static boolean couldKeyChangeOnImport(NetworkStatsCollection.Key key) {
+ if (key.ident.isEmpty()) return false;
+ final NetworkIdentity firstIdent = key.ident.iterator().next();
+
+ // Non-mobile network with non-empty RAT type.
+ // This combination is invalid and the NetworkIdentity.Builder will throw if it is passed
+ // in, but it looks like it was previously possible to persist it to disk. The importer sets
+ // the RAT type to NETWORK_TYPE_ALL.
+ if (firstIdent.getType() != ConnectivityManager.TYPE_MOBILE
+ && firstIdent.getRatType() != NetworkTemplate.NETWORK_TYPE_ALL) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Nullable
+ private static String compareStats(
+ NetworkStatsCollection migrated, NetworkStatsCollection legacy) {
+ final Map<NetworkStatsCollection.Key, NetworkStatsHistory> migEntries =
+ migrated.getEntries();
+ final Map<NetworkStatsCollection.Key, NetworkStatsHistory> legEntries = legacy.getEntries();
+
+ final ArraySet<NetworkStatsCollection.Key> unmatchedLegKeys =
+ new ArraySet<>(legEntries.keySet());
+
+ for (NetworkStatsCollection.Key legKey : legEntries.keySet()) {
+ final NetworkStatsHistory legHistory = legEntries.get(legKey);
+ final NetworkStatsHistory migHistory = migEntries.get(legKey);
+
+ if (migHistory == null && couldKeyChangeOnImport(legKey)) {
+ unmatchedLegKeys.remove(legKey);
+ continue;
+ }
+
+ if (migHistory == null) {
+ return "Missing migrated history for legacy key " + str(legKey)
+ + ", legacy history was " + legHistory;
+ }
+ if (!migHistory.isSameAs(legHistory)) {
+ return "Difference in history for key " + legKey + "; legacy history " + legHistory
+ + ", migrated history " + migHistory;
+ }
+ unmatchedLegKeys.remove(legKey);
+ }
+
+ if (!unmatchedLegKeys.isEmpty()) {
+ final NetworkStatsHistory first = legEntries.get(unmatchedLegKeys.valueAt(0));
+ return "Found unmatched legacy keys: count=" + unmatchedLegKeys.size()
+ + ", first unmatched collection " + first;
+ }
+
+ if (migrated.getStartMillis() != legacy.getStartMillis()
+ || migrated.getEndMillis() != legacy.getEndMillis()) {
+ return "Start / end of the collections "
+ + migrated.getStartMillis() + "/" + legacy.getStartMillis() + " and "
+ + migrated.getEndMillis() + "/" + legacy.getEndMillis()
+ + " don't match";
+ }
+
+ if (migrated.getTotalBytes() != legacy.getTotalBytes()) {
+ return "Total bytes " + migrated.getTotalBytes() + " and " + legacy.getTotalBytes()
+ + " don't match for collections with start/end "
+ + migrated.getStartMillis()
+ + "/" + legacy.getStartMillis();
+ }
+
+ return null;
+ }
+
+ @GuardedBy("mStatsLock")
+ @NonNull
+ private NetworkStatsCollection readPlatformCollectionForRecorder(
+ @NonNull final NetworkStatsRecorder rec) throws IOException {
+ final String prefix = rec.getCookie();
+ Log.i(TAG, "Importing platform collection for prefix " + prefix);
+ final NetworkStatsCollection collection = Objects.requireNonNull(
+ mDeps.readPlatformCollection(prefix, rec.getBucketDuration()),
+ "Imported platform collection for prefix " + prefix + " must not be null");
+
+ final long bootTimestamp = System.currentTimeMillis() - SystemClock.elapsedRealtime();
+ if (!collection.isEmpty() && bootTimestamp < collection.getStartMillis()) {
+ throw new IllegalArgumentException("Platform collection for prefix " + prefix
+ + " contains data that could not possibly come from the previous boot "
+ + "(start timestamp = " + Instant.ofEpochMilli(collection.getStartMillis())
+ + ", last booted at " + Instant.ofEpochMilli(bootTimestamp));
+ }
+
+ Log.i(TAG, "Successfully read platform collection spanning from "
+ // Instant uses ISO-8601 for toString()
+ + Instant.ofEpochMilli(collection.getStartMillis()).toString() + " to "
+ + Instant.ofEpochMilli(collection.getEndMillis()).toString());
+ return collection;
}
/**
@@ -2102,10 +2470,32 @@
return;
}
+ pw.println("Directory:");
+ pw.increaseIndent();
+ pw.println(mStatsDir);
+ pw.decreaseIndent();
+
pw.println("Configs:");
pw.increaseIndent();
pw.print(NETSTATS_COMBINE_SUBTYPE_ENABLED, mSettings.getCombineSubtypeEnabled());
pw.println();
+ pw.print(NETSTATS_STORE_FILES_IN_APEXDATA, mDeps.getStoreFilesInApexData());
+ pw.println();
+ pw.print(NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS, mDeps.getImportLegacyTargetAttempts());
+ pw.println();
+ if (mDeps.getStoreFilesInApexData()) {
+ try {
+ pw.print("platform legacy stats import attempts count",
+ mImportLegacyAttemptsCounter.get());
+ pw.println();
+ pw.print("platform legacy stats import successes count",
+ mImportLegacySuccessesCounter.get());
+ pw.println();
+ } catch (IOException e) {
+ pw.println("(failed to dump platform legacy stats import counters)");
+ }
+ }
+
pw.decreaseIndent();
pw.println("Active interfaces:");
diff --git a/service-t/src/com/android/server/net/PersistentInt.java b/service-t/src/com/android/server/net/PersistentInt.java
new file mode 100644
index 0000000..c212b77
--- /dev/null
+++ b/service-t/src/com/android/server/net/PersistentInt.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.AtomicFile;
+import android.util.SystemConfigFileCommitEventLogger;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * A simple integer backed by an on-disk {@link AtomicFile}. Not thread-safe.
+ */
+public class PersistentInt {
+ private final String mPath;
+ private final AtomicFile mFile;
+
+ /**
+ * Constructs a new {@code PersistentInt}. The counter is set to 0 if the file does not exist.
+ * Before returning, the constructor checks that the file is readable and writable. This
+ * indicates that in the future {@link #get} and {@link #set} are likely to succeed,
+ * though other events (data corruption, other code deleting the file, etc.) may cause these
+ * calls to fail in the future.
+ *
+ * @param path the path of the file to use.
+ * @param logger the logger
+ * @throws IOException the counter could not be read or written
+ */
+ public PersistentInt(@NonNull String path, @Nullable SystemConfigFileCommitEventLogger logger)
+ throws IOException {
+ mPath = path;
+ mFile = new AtomicFile(new File(path), logger);
+ checkReadWrite();
+ }
+
+ private void checkReadWrite() throws IOException {
+ int value;
+ try {
+ value = get();
+ } catch (FileNotFoundException e) {
+ // Counter does not exist. Attempt to initialize to 0.
+ // Note that we cannot tell here if the file does not exist or if opening it failed,
+ // because in Java both of those throw FileNotFoundException.
+ value = 0;
+ }
+ set(value);
+ get();
+ // No exceptions? Good.
+ }
+
+ /**
+ * Gets the current value.
+ *
+ * @return the current value of the counter.
+ * @throws IOException if reading the value failed.
+ */
+ public int get() throws IOException {
+ try (FileInputStream fin = mFile.openRead();
+ DataInputStream din = new DataInputStream(fin)) {
+ return din.readInt();
+ }
+ }
+
+ /**
+ * Sets the current value.
+ * @param value the value to set
+ * @throws IOException if writing the value failed.
+ */
+ public void set(int value) throws IOException {
+ FileOutputStream fout = null;
+ try {
+ fout = mFile.startWrite();
+ DataOutputStream dout = new DataOutputStream(fout);
+ dout.writeInt(value);
+ mFile.finishWrite(fout);
+ } catch (IOException e) {
+ if (fout != null) {
+ mFile.failWrite(fout);
+ }
+ throw e;
+ }
+ }
+
+ public String getPath() {
+ return mPath;
+ }
+}
diff --git a/service/Android.bp b/service/Android.bp
index 0393c79..91b9d1c 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -224,15 +224,9 @@
lint: { strict_updatability_linting: true },
}
-genrule {
+filegroup {
name: "connectivity-jarjar-rules",
- defaults: ["jarjar-rules-combine-defaults"],
- srcs: [
- ":framework-connectivity-jarjar-rules",
- ":service-connectivity-jarjar-gen",
- ":service-nearby-jarjar-gen",
- ],
- out: ["connectivity-jarjar-rules.txt"],
+ srcs: ["jarjar-rules.txt"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
@@ -243,45 +237,3 @@
srcs: ["src/com/android/server/BpfNetMaps.java"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
-
-java_genrule {
- name: "service-connectivity-jarjar-gen",
- tool_files: [
- ":service-connectivity-pre-jarjar",
- ":service-connectivity-tiramisu-pre-jarjar",
- "jarjar-excludes.txt",
- ],
- tools: [
- "jarjar-rules-generator",
- "dexdump",
- ],
- out: ["service_connectivity_jarjar_rules.txt"],
- cmd: "$(location jarjar-rules-generator) " +
- "--jars $(location :service-connectivity-pre-jarjar) " +
- "$(location :service-connectivity-tiramisu-pre-jarjar) " +
- "--prefix android.net.connectivity " +
- "--excludes $(location jarjar-excludes.txt) " +
- "--dexdump $(location dexdump) " +
- "--output $(out)",
- visibility: ["//visibility:private"],
-}
-
-java_genrule {
- name: "service-nearby-jarjar-gen",
- tool_files: [
- ":service-nearby-pre-jarjar",
- "jarjar-excludes.txt",
- ],
- tools: [
- "jarjar-rules-generator",
- "dexdump",
- ],
- out: ["service_nearby_jarjar_rules.txt"],
- cmd: "$(location jarjar-rules-generator) " +
- "--jars $(location :service-nearby-pre-jarjar) " +
- "--prefix com.android.server.nearby " +
- "--excludes $(location jarjar-excludes.txt) " +
- "--dexdump $(location dexdump) " +
- "--output $(out)",
- visibility: ["//visibility:private"],
-}
diff --git a/service/ServiceConnectivityResources/res/values-sq/strings.xml b/service/ServiceConnectivityResources/res/values-sq/strings.xml
index 385c75c..85bd84f 100644
--- a/service/ServiceConnectivityResources/res/values-sq/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sq/strings.xml
@@ -35,7 +35,7 @@
<string-array name="network_switch_type_name">
<item msgid="3004933964374161223">"të dhënat celulare"</item>
<item msgid="5624324321165953608">"Wi-Fi"</item>
- <item msgid="5667906231066981731">"Bluetooth"</item>
+ <item msgid="5667906231066981731">"Bluetooth-i"</item>
<item msgid="346574747471703768">"Eternet"</item>
<item msgid="5734728378097476003">"VPN"</item>
</string-array>
diff --git a/service/jarjar-excludes.txt b/service/jarjar-excludes.txt
deleted file mode 100644
index b0d6763..0000000
--- a/service/jarjar-excludes.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-# Classes loaded by SystemServer via their hardcoded name, so they can't be jarjared
-com\.android\.server\.ConnectivityServiceInitializer(\$.+)?
-com\.android\.server\.NetworkStatsServiceInitializer(\$.+)?
-
-# Do not jarjar com.android.server, as several unit tests fail because they lose
-# package-private visibility between jarjared and non-jarjared classes.
-# TODO: fix the tests and also jarjar com.android.server, or at least only exclude a package that
-# is specific to the module like com.android.server.connectivity
-com\.android\.server\..+
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
new file mode 100644
index 0000000..c7223fc
--- /dev/null
+++ b/service/jarjar-rules.txt
@@ -0,0 +1,123 @@
+# Classes in framework-connectivity are restricted to the android.net package.
+# This cannot be changed because it is harcoded in ART in S.
+# Any missing jarjar rule for framework-connectivity would be caught by the
+# build as an unexpected class outside of the android.net package.
+rule com.android.net.module.util.** android.net.connectivity.@0
+rule com.android.modules.utils.** android.net.connectivity.@0
+rule android.net.NetworkFactory* android.net.connectivity.@0
+
+# From modules-utils-preconditions
+rule com.android.internal.util.Preconditions* android.net.connectivity.@0
+
+# From framework-connectivity-shared-srcs
+rule android.util.LocalLog* android.net.connectivity.@0
+rule android.util.IndentingPrintWriter* android.net.connectivity.@0
+rule com.android.internal.util.IndentingPrintWriter* android.net.connectivity.@0
+rule com.android.internal.util.MessageUtils* android.net.connectivity.@0
+rule com.android.internal.util.WakeupMessage* android.net.connectivity.@0
+rule com.android.internal.util.FileRotator* android.net.connectivity.@0
+rule com.android.internal.util.ProcFileReader* android.net.connectivity.@0
+
+# From framework-connectivity-protos
+rule com.google.protobuf.** android.net.connectivity.@0
+rule android.service.** android.net.connectivity.@0
+
+rule android.sysprop.** com.android.connectivity.@0
+
+rule com.android.internal.messages.** com.android.connectivity.@0
+
+# From dnsresolver_aidl_interface (newer AIDLs should go to android.net.resolv.aidl)
+rule android.net.resolv.aidl.** com.android.connectivity.@0
+rule android.net.IDnsResolver* com.android.connectivity.@0
+rule android.net.ResolverHostsParcel* com.android.connectivity.@0
+rule android.net.ResolverOptionsParcel* com.android.connectivity.@0
+rule android.net.ResolverParamsParcel* com.android.connectivity.@0
+rule android.net.ResolverParamsParcel* com.android.connectivity.@0
+# Also includes netd event listener AIDL, but this is handled by netd-client rules
+
+# From netd-client (newer AIDLs should go to android.net.netd.aidl)
+rule android.net.netd.aidl.** com.android.connectivity.@0
+# Avoid including android.net.INetdEventCallback, used in tests but not part of the module
+rule android.net.INetd com.android.connectivity.@0
+rule android.net.INetd$* com.android.connectivity.@0
+rule android.net.INetdUnsolicitedEventListener* com.android.connectivity.@0
+rule android.net.InterfaceConfigurationParcel* com.android.connectivity.@0
+rule android.net.MarkMaskParcel* com.android.connectivity.@0
+rule android.net.NativeNetworkConfig* com.android.connectivity.@0
+rule android.net.NativeNetworkType* com.android.connectivity.@0
+rule android.net.NativeVpnType* com.android.connectivity.@0
+rule android.net.RouteInfoParcel* com.android.connectivity.@0
+rule android.net.TetherConfigParcel* com.android.connectivity.@0
+rule android.net.TetherOffloadRuleParcel* com.android.connectivity.@0
+rule android.net.TetherStatsParcel* com.android.connectivity.@0
+rule android.net.UidRangeParcel* com.android.connectivity.@0
+rule android.net.metrics.INetdEventListener* com.android.connectivity.@0
+
+# From netlink-client
+rule android.net.netlink.** com.android.connectivity.@0
+
+# From networkstack-client (newer AIDLs should go to android.net.[networkstack|ipmemorystore].aidl)
+rule android.net.networkstack.aidl.** com.android.connectivity.@0
+rule android.net.ipmemorystore.aidl.** com.android.connectivity.@0
+rule android.net.ipmemorystore.aidl.** com.android.connectivity.@0
+rule android.net.DataStallReportParcelable* com.android.connectivity.@0
+rule android.net.DhcpResultsParcelable* com.android.connectivity.@0
+rule android.net.IIpMemoryStore* com.android.connectivity.@0
+rule android.net.INetworkMonitor* com.android.connectivity.@0
+rule android.net.INetworkStackConnector* com.android.connectivity.@0
+rule android.net.INetworkStackStatusCallback* com.android.connectivity.@0
+rule android.net.InformationElementParcelable* com.android.connectivity.@0
+rule android.net.InitialConfigurationParcelable* com.android.connectivity.@0
+rule android.net.IpMemoryStore* com.android.connectivity.@0
+rule android.net.Layer2InformationParcelable* com.android.connectivity.@0
+rule android.net.Layer2PacketParcelable* com.android.connectivity.@0
+rule android.net.NattKeepalivePacketDataParcelable* com.android.connectivity.@0
+rule android.net.NetworkMonitorManager* com.android.connectivity.@0
+rule android.net.NetworkTestResultParcelable* com.android.connectivity.@0
+rule android.net.PrivateDnsConfigParcel* com.android.connectivity.@0
+rule android.net.ProvisioningConfigurationParcelable* com.android.connectivity.@0
+rule android.net.ScanResultInfoParcelable* com.android.connectivity.@0
+rule android.net.TcpKeepalivePacketDataParcelable* com.android.connectivity.@0
+rule android.net.dhcp.DhcpLeaseParcelable* com.android.connectivity.@0
+rule android.net.dhcp.DhcpServingParamsParcel* com.android.connectivity.@0
+rule android.net.dhcp.IDhcpEventCallbacks* com.android.connectivity.@0
+rule android.net.dhcp.IDhcpServer* com.android.connectivity.@0
+rule android.net.ip.IIpClient* com.android.connectivity.@0
+rule android.net.ip.IpClientCallbacks* com.android.connectivity.@0
+rule android.net.ip.IpClientManager* com.android.connectivity.@0
+rule android.net.ip.IpClientUtil* com.android.connectivity.@0
+rule android.net.ipmemorystore.** com.android.connectivity.@0
+rule android.net.networkstack.** com.android.connectivity.@0
+rule android.net.shared.** com.android.connectivity.@0
+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
+rule android.net.util.NetworkConstants* com.android.connectivity.@0
+
+# From modules-utils-statemachine
+rule com.android.internal.util.IState* com.android.connectivity.@0
+rule com.android.internal.util.State* com.android.connectivity.@0
+
+# From the API shims
+rule com.android.networkstack.apishim.** com.android.connectivity.@0
+
+# From filegroup framework-connectivity-protos
+rule android.service.*Proto com.android.connectivity.@0
+
+# From mdns-aidl-interface
+rule android.net.mdns.aidl.** android.net.connectivity.@0
+
+# From nearby-service, including proto
+rule service.proto.** com.android.server.nearby.@0
+rule androidx.annotation.Keep* com.android.server.nearby.@0
+rule androidx.collection.** com.android.server.nearby.@0
+rule androidx.core.** com.android.server.nearby.@0
+rule androidx.versionedparcelable.** com.android.server.nearby.@0
+rule com.google.common.** com.android.server.nearby.@0
+
+# Remaining are connectivity sources in com.android.server and com.android.server.connectivity:
+# TODO: move to a subpackage of com.android.connectivity (such as com.android.connectivity.server)
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index 7b1f59c..bc70c93 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -39,152 +39,126 @@
namespace android {
-static void native_init(JNIEnv* env, jobject clazz) {
+#define CHECK_LOG(status) \
+ do { \
+ if (!isOk(status)) \
+ ALOGE("%s failed, error code = %d", __func__, status.code()); \
+ } while (0)
+
+static void native_init(JNIEnv* env, jclass clazz) {
Status status = mTc.start();
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
}
-static jint native_addNaughtyApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_addNaughtyApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
TrafficController::IptOp::IptOpInsert);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_removeNaughtyApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_removeNaughtyApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
TrafficController::IptOp::IptOpDelete);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_addNiceApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_addNiceApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
TrafficController::IptOp::IptOpInsert);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_removeNiceApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_removeNiceApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
TrafficController::IptOp::IptOpDelete);
- if (!isOk(status)) {
- ALOGD("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_setChildChain(JNIEnv* env, jobject clazz, jint childChain, jboolean enable) {
+static jint native_setChildChain(JNIEnv* env, jobject self, jint childChain, jboolean enable) {
auto chain = static_cast<ChildChain>(childChain);
int res = mTc.toggleUidOwnerMap(chain, enable);
- if (res) {
- ALOGE("%s failed, error code = %d", __func__, res);
- }
+ if (res) ALOGE("%s failed, error code = %d", __func__, res);
return (jint)res;
}
-static jint native_replaceUidChain(JNIEnv* env, jobject clazz, jstring name, jboolean isAllowlist,
- jintArray jUids) {
+static jint native_replaceUidChain(JNIEnv* env, jobject self, jstring name, jboolean isAllowlist,
+ jintArray jUids) {
const ScopedUtfChars chainNameUtf8(env, name);
- if (chainNameUtf8.c_str() == nullptr) {
- return -EINVAL;
- }
+ if (chainNameUtf8.c_str() == nullptr) return -EINVAL;
const std::string chainName(chainNameUtf8.c_str());
ScopedIntArrayRO uids(env, jUids);
- if (uids.get() == nullptr) {
- return -EINVAL;
- }
+ if (uids.get() == nullptr) return -EINVAL;
size_t size = uids.size();
static_assert(sizeof(*(uids.get())) == sizeof(int32_t));
std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
int res = mTc.replaceUidOwnerMap(chainName, isAllowlist, data);
- if (res) {
- ALOGE("%s failed, error code = %d", __func__, res);
- }
+ if (res) ALOGE("%s failed, error code = %d", __func__, res);
return (jint)res;
}
-static jint native_setUidRule(JNIEnv* env, jobject clazz, jint childChain, jint uid,
- jint firewallRule) {
+static jint native_setUidRule(JNIEnv* env, jobject self, jint childChain, jint uid,
+ jint firewallRule) {
auto chain = static_cast<ChildChain>(childChain);
auto rule = static_cast<FirewallRule>(firewallRule);
FirewallType fType = mTc.getFirewallType(chain);
int res = mTc.changeUidOwnerRule(chain, uid, rule, fType);
- if (res) {
- ALOGE("%s failed, error code = %d", __func__, res);
- }
+ if (res) ALOGE("%s failed, error code = %d", __func__, res);
return (jint)res;
}
-static jint native_addUidInterfaceRules(JNIEnv* env, jobject clazz, jstring ifName,
- jintArray jUids) {
+static jint native_addUidInterfaceRules(JNIEnv* env, jobject self, jstring ifName,
+ jintArray jUids) {
// Null ifName is a wildcard to allow apps to receive packets on all interfaces and ifIndex is
// set to 0.
- int ifIndex;
+ int ifIndex = 0;
if (ifName != nullptr) {
const ScopedUtfChars ifNameUtf8(env, ifName);
const std::string interfaceName(ifNameUtf8.c_str());
ifIndex = if_nametoindex(interfaceName.c_str());
- } else {
- ifIndex = 0;
}
ScopedIntArrayRO uids(env, jUids);
- if (uids.get() == nullptr) {
- return -EINVAL;
- }
+ if (uids.get() == nullptr) return -EINVAL;
size_t size = uids.size();
static_assert(sizeof(*(uids.get())) == sizeof(int32_t));
std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
Status status = mTc.addUidInterfaceRules(ifIndex, data);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_removeUidInterfaceRules(JNIEnv* env, jobject clazz, jintArray jUids) {
+static jint native_removeUidInterfaceRules(JNIEnv* env, jobject self, jintArray jUids) {
ScopedIntArrayRO uids(env, jUids);
- if (uids.get() == nullptr) {
- return -EINVAL;
- }
+ if (uids.get() == nullptr) return -EINVAL;
size_t size = uids.size();
static_assert(sizeof(*(uids.get())) == sizeof(int32_t));
std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
Status status = mTc.removeUidInterfaceRules(data);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_swapActiveStatsMap(JNIEnv* env, jobject clazz) {
+static jint native_swapActiveStatsMap(JNIEnv* env, jobject self) {
Status status = mTc.swapActiveStatsMap();
- if (!isOk(status)) {
- ALOGD("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static void native_setPermissionForUids(JNIEnv* env, jobject clazz, jint permission,
- jintArray jUids) {
+static void native_setPermissionForUids(JNIEnv* env, jobject self, jint permission,
+ jintArray jUids) {
ScopedIntArrayRO uids(env, jUids);
if (uids.get() == nullptr) return;
@@ -194,7 +168,7 @@
mTc.setPermissionForUids(permission, data);
}
-static void native_dump(JNIEnv* env, jobject clazz, jobject javaFd, jboolean verbose) {
+static void native_dump(JNIEnv* env, jobject self, jobject javaFd, jboolean verbose) {
int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
if (fd < 0) {
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
@@ -239,9 +213,8 @@
// clang-format on
int register_com_android_server_BpfNetMaps(JNIEnv* env) {
- return jniRegisterNativeMethods(env,
- "com/android/server/BpfNetMaps",
- gMethods, NELEM(gMethods));
+ return jniRegisterNativeMethods(env, "com/android/server/BpfNetMaps",
+ gMethods, NELEM(gMethods));
}
}; // namespace android
diff --git a/service/native/Android.bp b/service/native/Android.bp
index cb26bc3..697fcbd 100644
--- a/service/native/Android.bp
+++ b/service/native/Android.bp
@@ -52,7 +52,8 @@
cc_test {
name: "traffic_controller_unit_test",
- test_suites: ["general-tests"],
+ test_suites: ["general-tests", "mts-tethering"],
+ test_config_template: ":net_native_test_config_template",
require_root: true,
local_include_dirs: ["include"],
header_libs: [
@@ -71,4 +72,13 @@
"libnetd_updatable",
"netd_aidl_interface-lateststable-ndk",
],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
}
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 5581c40..d05e6fa 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -74,6 +74,9 @@
const char* TrafficController::LOCAL_POWERSAVE = "fw_powersave";
const char* TrafficController::LOCAL_RESTRICTED = "fw_restricted";
const char* TrafficController::LOCAL_LOW_POWER_STANDBY = "fw_low_power_standby";
+const char* TrafficController::LOCAL_OEM_DENY_1 = "fw_oem_deny_1";
+const char* TrafficController::LOCAL_OEM_DENY_2 = "fw_oem_deny_2";
+const char* TrafficController::LOCAL_OEM_DENY_3 = "fw_oem_deny_3";
static_assert(BPF_PERMISSION_INTERNET == INetd::PERMISSION_INTERNET,
"Mismatch between BPF and AIDL permissions: PERMISSION_INTERNET");
@@ -99,6 +102,9 @@
FLAG_MSG_TRANS(matchType, LOW_POWER_STANDBY_MATCH, match);
FLAG_MSG_TRANS(matchType, IIF_MATCH, match);
FLAG_MSG_TRANS(matchType, LOCKDOWN_VPN_MATCH, match);
+ FLAG_MSG_TRANS(matchType, OEM_DENY_1_MATCH, match);
+ FLAG_MSG_TRANS(matchType, OEM_DENY_2_MATCH, match);
+ FLAG_MSG_TRANS(matchType, OEM_DENY_3_MATCH, match);
if (match) {
return StringPrintf("Unknown match: %u", match);
}
@@ -184,6 +190,7 @@
RETURN_IF_NOT_OK(mUidOwnerMap.init(UID_OWNER_MAP_PATH));
RETURN_IF_NOT_OK(mUidOwnerMap.clear());
RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+ ALOGI("%s successfully", __func__);
return netdutils::status::ok;
}
@@ -335,6 +342,12 @@
return ALLOWLIST;
case LOCKDOWN:
return DENYLIST;
+ case OEM_DENY_1:
+ return DENYLIST;
+ case OEM_DENY_2:
+ return DENYLIST;
+ case OEM_DENY_3:
+ return DENYLIST;
case NONE:
default:
return DENYLIST;
@@ -363,6 +376,15 @@
case LOCKDOWN:
res = updateOwnerMapEntry(LOCKDOWN_VPN_MATCH, uid, rule, type);
break;
+ case OEM_DENY_1:
+ res = updateOwnerMapEntry(OEM_DENY_1_MATCH, uid, rule, type);
+ break;
+ case OEM_DENY_2:
+ res = updateOwnerMapEntry(OEM_DENY_2_MATCH, uid, rule, type);
+ break;
+ case OEM_DENY_3:
+ res = updateOwnerMapEntry(OEM_DENY_3_MATCH, uid, rule, type);
+ break;
case NONE:
default:
ALOGW("Unknown child chain: %d", chain);
@@ -440,6 +462,12 @@
res = replaceRulesInMap(RESTRICTED_MATCH, uids);
} else if (!name.compare(LOCAL_LOW_POWER_STANDBY)) {
res = replaceRulesInMap(LOW_POWER_STANDBY_MATCH, uids);
+ } else if (!name.compare(LOCAL_OEM_DENY_1)) {
+ res = replaceRulesInMap(OEM_DENY_1_MATCH, uids);
+ } else if (!name.compare(LOCAL_OEM_DENY_2)) {
+ res = replaceRulesInMap(OEM_DENY_2_MATCH, uids);
+ } else if (!name.compare(LOCAL_OEM_DENY_3)) {
+ res = replaceRulesInMap(OEM_DENY_3_MATCH, uids);
} else {
ALOGE("unknown chain name: %s", name.c_str());
return -EINVAL;
@@ -454,15 +482,15 @@
int TrafficController::toggleUidOwnerMap(ChildChain chain, bool enable) {
std::lock_guard guard(mMutex);
uint32_t key = UID_RULES_CONFIGURATION_KEY;
- auto oldConfiguration = mConfigurationMap.readValue(key);
- if (!oldConfiguration.ok()) {
+ auto oldConfigure = mConfigurationMap.readValue(key);
+ if (!oldConfigure.ok()) {
ALOGE("Cannot read the old configuration from map: %s",
- oldConfiguration.error().message().c_str());
- return -oldConfiguration.error().code();
+ oldConfigure.error().message().c_str());
+ return -oldConfigure.error().code();
}
Status res;
BpfConfig newConfiguration;
- uint8_t match;
+ uint32_t match;
switch (chain) {
case DOZABLE:
match = DOZABLE_MATCH;
@@ -479,11 +507,20 @@
case LOW_POWER_STANDBY:
match = LOW_POWER_STANDBY_MATCH;
break;
+ case OEM_DENY_1:
+ match = OEM_DENY_1_MATCH;
+ break;
+ case OEM_DENY_2:
+ match = OEM_DENY_2_MATCH;
+ break;
+ case OEM_DENY_3:
+ match = OEM_DENY_3_MATCH;
+ break;
default:
return -EINVAL;
}
newConfiguration =
- enable ? (oldConfiguration.value() | match) : (oldConfiguration.value() & (~match));
+ enable ? (oldConfigure.value() | match) : (oldConfigure.value() & (~match));
res = mConfigurationMap.writeValue(key, newConfiguration, BPF_EXIST);
if (!isOk(res)) {
ALOGE("Failed to toggleUidOwnerMap(%d): %s", chain, res.msg().c_str());
@@ -495,17 +532,17 @@
std::lock_guard guard(mMutex);
uint32_t key = CURRENT_STATS_MAP_CONFIGURATION_KEY;
- auto oldConfiguration = mConfigurationMap.readValue(key);
- if (!oldConfiguration.ok()) {
+ auto oldConfigure = mConfigurationMap.readValue(key);
+ if (!oldConfigure.ok()) {
ALOGE("Cannot read the old configuration from map: %s",
- oldConfiguration.error().message().c_str());
- return Status(oldConfiguration.error().code(), oldConfiguration.error().message());
+ oldConfigure.error().message().c_str());
+ return Status(oldConfigure.error().code(), oldConfigure.error().message());
}
// Write to the configuration map to inform the kernel eBPF program to switch
// from using one map to the other. Use flag BPF_EXIST here since the map should
// be already populated in initMaps.
- uint8_t newConfigure = (oldConfiguration.value() == SELECT_MAP_A) ? SELECT_MAP_B : SELECT_MAP_A;
+ uint32_t newConfigure = (oldConfigure.value() == SELECT_MAP_A) ? SELECT_MAP_B : SELECT_MAP_A;
auto res = mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, newConfigure,
BPF_EXIST);
if (!res.ok()) {
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index ad53cb8..1aca633 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -30,12 +30,15 @@
#include <gtest/gtest.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <binder/Status.h>
#include <netdutils/MockSyscalls.h>
+#define TEST_BPF_MAP
#include "TrafficController.h"
#include "bpf/BpfUtils.h"
#include "NetdUpdatablePublic.h"
@@ -48,6 +51,7 @@
using android::netdutils::Status;
using base::Result;
using netdutils::isOk;
+using netdutils::statusFromErrno;
constexpr int TEST_MAP_SIZE = 10;
constexpr uid_t TEST_UID = 10086;
@@ -55,6 +59,13 @@
constexpr uid_t TEST_UID3 = 98765;
constexpr uint32_t TEST_TAG = 42;
constexpr uint32_t TEST_COUNTERSET = 1;
+constexpr int TEST_COOKIE = 1;
+constexpr char TEST_IFNAME[] = "test0";
+constexpr int TEST_IFINDEX = 999;
+constexpr int RXPACKETS = 1;
+constexpr int RXBYTES = 100;
+constexpr int TXPACKETS = 0;
+constexpr int TXBYTES = 0;
#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
@@ -65,73 +76,88 @@
BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
- BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
+ BpfMap<uint32_t, uint32_t> mFakeConfigurationMap;
BpfMap<uint32_t, UidOwnerValue> mFakeUidOwnerMap;
BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
+ BpfMap<uint32_t, uint8_t> mFakeUidCounterSetMap;
+ BpfMap<uint32_t, IfaceValue> mFakeIfaceIndexNameMap;
void SetUp() {
std::lock_guard guard(mTc.mMutex);
ASSERT_EQ(0, setrlimitForTest());
- mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(UidTagValue),
- TEST_MAP_SIZE, 0));
+ mFakeCookieTagMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeCookieTagMap);
- mFakeAppUidStatsMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(StatsValue),
- TEST_MAP_SIZE, 0));
+ mFakeAppUidStatsMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeAppUidStatsMap);
- mFakeStatsMapA.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(StatsKey), sizeof(StatsValue),
- TEST_MAP_SIZE, 0));
+ mFakeStatsMapA.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeStatsMapA);
- mFakeConfigurationMap.reset(
- createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), 1, 0));
+ mFakeConfigurationMap.resetMap(BPF_MAP_TYPE_HASH, 1);
ASSERT_VALID(mFakeConfigurationMap);
- mFakeUidOwnerMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(UidOwnerValue),
- TEST_MAP_SIZE, 0));
+ mFakeUidOwnerMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeUidOwnerMap);
- mFakeUidPermissionMap.reset(
- createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+ mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeUidPermissionMap);
- mTc.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
+ mFakeUidCounterSetMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
+ ASSERT_VALID(mFakeUidCounterSetMap);
+
+ mFakeIfaceIndexNameMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
+ ASSERT_VALID(mFakeIfaceIndexNameMap);
+
+ mTc.mCookieTagMap = mFakeCookieTagMap;
ASSERT_VALID(mTc.mCookieTagMap);
- mTc.mAppUidStatsMap.reset(dupFd(mFakeAppUidStatsMap.getMap()));
+ mTc.mAppUidStatsMap = mFakeAppUidStatsMap;
ASSERT_VALID(mTc.mAppUidStatsMap);
- mTc.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
+ mTc.mStatsMapA = mFakeStatsMapA;
ASSERT_VALID(mTc.mStatsMapA);
- mTc.mConfigurationMap.reset(dupFd(mFakeConfigurationMap.getMap()));
+ mTc.mConfigurationMap = mFakeConfigurationMap;
ASSERT_VALID(mTc.mConfigurationMap);
// Always write to stats map A by default.
ASSERT_RESULT_OK(mTc.mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY,
SELECT_MAP_A, BPF_ANY));
- mTc.mUidOwnerMap.reset(dupFd(mFakeUidOwnerMap.getMap()));
+ mTc.mUidOwnerMap = mFakeUidOwnerMap;
ASSERT_VALID(mTc.mUidOwnerMap);
- mTc.mUidPermissionMap.reset(dupFd(mFakeUidPermissionMap.getMap()));
+ mTc.mUidPermissionMap = mFakeUidPermissionMap;
ASSERT_VALID(mTc.mUidPermissionMap);
mTc.mPrivilegedUser.clear();
- }
- int dupFd(const android::base::unique_fd& mapFd) {
- return fcntl(mapFd.get(), F_DUPFD_CLOEXEC, 0);
+ mTc.mUidCounterSetMap = mFakeUidCounterSetMap;
+ ASSERT_VALID(mTc.mUidCounterSetMap);
+
+ mTc.mIfaceIndexNameMap = mFakeIfaceIndexNameMap;
+ ASSERT_VALID(mTc.mIfaceIndexNameMap);
}
void populateFakeStats(uint64_t cookie, uint32_t uid, uint32_t tag, StatsKey* key) {
UidTagValue cookieMapkey = {.uid = (uint32_t)uid, .tag = tag};
EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY));
- *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
- StatsValue statsMapValue = {.rxPackets = 1, .rxBytes = 100};
- EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
- key->tag = 0;
+ *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = TEST_IFINDEX};
+ StatsValue statsMapValue = {.rxPackets = RXPACKETS, .rxBytes = RXBYTES,
+ .txPackets = TXPACKETS, .txBytes = TXBYTES};
EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
EXPECT_RESULT_OK(mFakeAppUidStatsMap.writeValue(uid, statsMapValue, BPF_ANY));
// put tag information back to statsKey
key->tag = tag;
}
+ void populateFakeCounterSet(uint32_t uid, uint32_t counterSet) {
+ EXPECT_RESULT_OK(mFakeUidCounterSetMap.writeValue(uid, counterSet, BPF_ANY));
+ }
+
+ void populateFakeIfaceIndexName(const char* name, uint32_t ifaceIndex) {
+ if (name == nullptr || ifaceIndex <= 0) return;
+
+ IfaceValue iface;
+ strlcpy(iface.name, name, sizeof(IfaceValue));
+ EXPECT_RESULT_OK(mFakeIfaceIndexNameMap.writeValue(ifaceIndex, iface, BPF_ANY));
+ }
+
void checkUidOwnerRuleForChain(ChildChain chain, UidOwnerMatchType match) {
uint32_t uid = TEST_UID;
EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, DENYLIST));
@@ -251,17 +277,17 @@
EXPECT_EQ(tag, cookieMapResult.value().tag);
Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
EXPECT_RESULT_OK(statsMapResult);
- EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
- EXPECT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
+ EXPECT_EQ((uint64_t)RXPACKETS, statsMapResult.value().rxPackets);
+ EXPECT_EQ((uint64_t)RXBYTES, statsMapResult.value().rxBytes);
tagStatsMapKey.tag = 0;
statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
EXPECT_RESULT_OK(statsMapResult);
- EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
- EXPECT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
+ EXPECT_EQ((uint64_t)RXPACKETS, statsMapResult.value().rxPackets);
+ EXPECT_EQ((uint64_t)RXBYTES, statsMapResult.value().rxBytes);
auto appStatsResult = mFakeAppUidStatsMap.readValue(uid);
EXPECT_RESULT_OK(appStatsResult);
- EXPECT_EQ((uint64_t)1, appStatsResult.value().rxPackets);
- EXPECT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
+ EXPECT_EQ((uint64_t)RXPACKETS, appStatsResult.value().rxPackets);
+ EXPECT_EQ((uint64_t)RXBYTES, appStatsResult.value().rxBytes);
}
Status updateUidOwnerMaps(const std::vector<uint32_t>& appUids,
@@ -274,6 +300,62 @@
return ret;
}
+ Status dump(bool verbose, std::vector<std::string>& outputLines) {
+ if (!outputLines.empty()) return statusFromErrno(EUCLEAN, "Output buffer is not empty");
+
+ android::base::unique_fd localFd, remoteFd;
+ if (!Pipe(&localFd, &remoteFd)) return statusFromErrno(errno, "Failed on pipe");
+
+ // dump() blocks until another thread has consumed all its output.
+ std::thread dumpThread =
+ std::thread([this, remoteFd{std::move(remoteFd)}, verbose]() {
+ mTc.dump(remoteFd, verbose);
+ });
+
+ std::string dumpContent;
+ if (!android::base::ReadFdToString(localFd.get(), &dumpContent)) {
+ return statusFromErrno(errno, "Failed to read dump results from fd");
+ }
+ dumpThread.join();
+
+ std::stringstream dumpStream(std::move(dumpContent));
+ std::string line;
+ while (std::getline(dumpStream, line)) {
+ outputLines.push_back(line);
+ }
+
+ return netdutils::status::ok;
+ }
+
+ // Strings in the |expect| must exist in dump results in order. But no need to be consecutive.
+ bool expectDumpsysContains(std::vector<std::string>& expect) {
+ if (expect.empty()) return false;
+
+ std::vector<std::string> output;
+ Status result = dump(true, output);
+ if (!isOk(result)) {
+ GTEST_LOG_(ERROR) << "TrafficController dump failed: " << netdutils::toString(result);
+ return false;
+ }
+
+ int matched = 0;
+ auto it = expect.begin();
+ for (const auto& line : output) {
+ if (it == expect.end()) break;
+ if (std::string::npos != line.find(*it)) {
+ matched++;
+ ++it;
+ }
+ }
+
+ if (matched != expect.size()) {
+ // dump results for debugging
+ for (const auto& o : output) LOG(INFO) << "output: " << o;
+ for (const auto& e : expect) LOG(INFO) << "expect: " << e;
+ return false;
+ }
+ return true;
+ }
};
TEST_F(TrafficControllerTest, TestUpdateOwnerMapEntry) {
@@ -308,6 +390,9 @@
checkUidOwnerRuleForChain(RESTRICTED, RESTRICTED_MATCH);
checkUidOwnerRuleForChain(LOW_POWER_STANDBY, LOW_POWER_STANDBY_MATCH);
checkUidOwnerRuleForChain(LOCKDOWN, LOCKDOWN_VPN_MATCH);
+ checkUidOwnerRuleForChain(OEM_DENY_1, OEM_DENY_1_MATCH);
+ checkUidOwnerRuleForChain(OEM_DENY_2, OEM_DENY_2_MATCH);
+ checkUidOwnerRuleForChain(OEM_DENY_3, OEM_DENY_3_MATCH);
ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(NONE, TEST_UID, ALLOW, ALLOWLIST));
ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(INVALID_CHAIN, TEST_UID, ALLOW, ALLOWLIST));
}
@@ -319,6 +404,9 @@
checkUidMapReplace("fw_powersave", uids, POWERSAVE_MATCH);
checkUidMapReplace("fw_restricted", uids, RESTRICTED_MATCH);
checkUidMapReplace("fw_low_power_standby", uids, LOW_POWER_STANDBY_MATCH);
+ checkUidMapReplace("fw_oem_deny_1", uids, OEM_DENY_1_MATCH);
+ checkUidMapReplace("fw_oem_deny_2", uids, OEM_DENY_2_MATCH);
+ checkUidMapReplace("fw_oem_deny_3", uids, OEM_DENY_3_MATCH);
ASSERT_EQ(-EINVAL, mTc.replaceUidOwnerMap("unknow", true, uids));
}
@@ -650,6 +738,83 @@
expectPrivilegedUserSetEmpty();
}
+TEST_F(TrafficControllerTest, TestDumpsys) {
+ StatsKey tagStatsMapKey;
+ populateFakeStats(TEST_COOKIE, TEST_UID, TEST_TAG, &tagStatsMapKey);
+ populateFakeCounterSet(TEST_UID3, TEST_COUNTERSET);
+
+ // Expect: (part of this depends on hard-code values in populateFakeStats())
+ //
+ // mCookieTagMap:
+ // cookie=1 tag=0x2a uid=10086
+ //
+ // mUidCounterSetMap:
+ // 98765 1
+ //
+ // mAppUidStatsMap::
+ // uid rxBytes rxPackets txBytes txPackets
+ // 10086 100 1 0 0
+ //
+ // mStatsMapA:
+ // ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets
+ // 999 test0 0x2a 10086 1 100 1 0 0
+ std::vector<std::string> expectedLines = {
+ "mCookieTagMap:",
+ fmt::format("cookie={} tag={:#x} uid={}", TEST_COOKIE, TEST_TAG, TEST_UID),
+ "mUidCounterSetMap:",
+ fmt::format("{} {}", TEST_UID3, TEST_COUNTERSET),
+ "mAppUidStatsMap::", // TODO@: fix double colon
+ "uid rxBytes rxPackets txBytes txPackets",
+ fmt::format("{} {} {} {} {}", TEST_UID, RXBYTES, RXPACKETS, TXBYTES, TXPACKETS),
+ "mStatsMapA",
+ "ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets",
+ fmt::format("{} {} {:#x} {} {} {} {} {} {}",
+ TEST_IFINDEX, TEST_IFNAME, TEST_TAG, TEST_UID, TEST_COUNTERSET, RXBYTES,
+ RXPACKETS, TXBYTES, TXPACKETS)};
+
+ populateFakeIfaceIndexName(TEST_IFNAME, TEST_IFINDEX);
+ expectedLines.emplace_back("mIfaceIndexNameMap:");
+ expectedLines.emplace_back(fmt::format("ifaceIndex={} ifaceName={}",
+ TEST_IFINDEX, TEST_IFNAME));
+
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, HAPPY_BOX_MATCH,
+ TrafficController::IptOpInsert)));
+ expectedLines.emplace_back("mUidOwnerMap:");
+ expectedLines.emplace_back(fmt::format("{} HAPPY_BOX_MATCH", TEST_UID));
+
+ mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, {TEST_UID2});
+ expectedLines.emplace_back("mUidPermissionMap:");
+ expectedLines.emplace_back(fmt::format("{} BPF_PERMISSION_UPDATE_DEVICE_STATS", TEST_UID2));
+ expectedLines.emplace_back("mPrivilegedUser:");
+ expectedLines.emplace_back(fmt::format("{} ALLOW_UPDATE_DEVICE_STATS", TEST_UID2));
+ EXPECT_TRUE(expectDumpsysContains(expectedLines));
+}
+
+TEST_F(TrafficControllerTest, getFirewallType) {
+ static const struct TestConfig {
+ ChildChain childChain;
+ FirewallType firewallType;
+ } testConfigs[] = {
+ // clang-format off
+ {NONE, DENYLIST},
+ {DOZABLE, ALLOWLIST},
+ {STANDBY, DENYLIST},
+ {POWERSAVE, ALLOWLIST},
+ {RESTRICTED, ALLOWLIST},
+ {LOW_POWER_STANDBY, ALLOWLIST},
+ {LOCKDOWN, DENYLIST},
+ {OEM_DENY_1, DENYLIST},
+ {OEM_DENY_2, DENYLIST},
+ {INVALID_CHAIN, DENYLIST},
+ // clang-format on
+ };
+
+ for (const auto& config : testConfigs) {
+ SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.childChain, config.firewallType));
+ EXPECT_EQ(config.firewallType, mTc.getFirewallType(config.childChain));
+ }
+}
+
constexpr uint32_t SOCK_CLOSE_WAIT_US = 30 * 1000;
constexpr uint32_t ENOBUFS_POLL_WAIT_US = 10 * 1000;
@@ -673,7 +838,7 @@
BpfMap<uint64_t, UidTagValue> mCookieTagMap;
void SetUp() {
- mCookieTagMap.reset(android::bpf::mapRetrieveRW(COOKIE_TAG_MAP_PATH));
+ mCookieTagMap.init(COOKIE_TAG_MAP_PATH);
ASSERT_TRUE(mCookieTagMap.isValid());
}
@@ -685,7 +850,7 @@
if (res.ok() || (res.error().code() == ENOENT)) {
return Result<void>();
}
- ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key,
+ ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s", key,
strerror(res.error().code()));
}
// Move forward to next cookie in the map.
diff --git a/service/native/include/Common.h b/service/native/include/Common.h
index 847acec..2427aa9 100644
--- a/service/native/include/Common.h
+++ b/service/native/include/Common.h
@@ -36,6 +36,9 @@
RESTRICTED = 4,
LOW_POWER_STANDBY = 5,
LOCKDOWN = 6,
+ OEM_DENY_1 = 7,
+ OEM_DENY_2 = 8,
+ OEM_DENY_3 = 9,
INVALID_CHAIN
};
// LINT.ThenChange(packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java)
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index 79e75ac..c019ce7 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -88,6 +88,9 @@
static const char* LOCAL_POWERSAVE;
static const char* LOCAL_RESTRICTED;
static const char* LOCAL_LOW_POWER_STANDBY;
+ static const char* LOCAL_OEM_DENY_1;
+ static const char* LOCAL_OEM_DENY_2;
+ static const char* LOCAL_OEM_DENY_3;
private:
/*
@@ -149,13 +152,13 @@
* the map right now:
* - Entry with UID_RULES_CONFIGURATION_KEY:
* Store the configuration for the current uid rules. It indicates the device
- * is in doze/powersave/standby/restricted/low power standby mode.
+ * is in doze/powersave/standby/restricted/low power standby/oem deny mode.
* - Entry with CURRENT_STATS_MAP_CONFIGURATION_KEY:
* Stores the current live stats map that kernel program is writing to.
* Userspace can do scraping and cleaning job on the other one depending on the
* current configs.
*/
- bpf::BpfMap<uint32_t, uint8_t> mConfigurationMap GUARDED_BY(mMutex);
+ bpf::BpfMap<uint32_t, uint32_t> mConfigurationMap GUARDED_BY(mMutex);
/*
* mUidOwnerMap: Store uids that are used for bandwidth control uid match.
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
index 68e4dc4..54d40ac 100644
--- a/service/native/libs/libclat/Android.bp
+++ b/service/native/libs/libclat/Android.bp
@@ -35,7 +35,8 @@
cc_test {
name: "libclat_test",
defaults: ["netd_defaults"],
- test_suites: ["device-tests"],
+ test_suites: ["general-tests", "mts-tethering"],
+ test_config_template: ":net_native_test_config_template",
srcs: [
"clatutils_test.cpp",
],
@@ -49,5 +50,14 @@
"liblog",
"libnetutils",
],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
require_root: true,
}
diff --git a/service/native/libs/libclat/clatutils_test.cpp b/service/native/libs/libclat/clatutils_test.cpp
index 4153e19..8cca1f4 100644
--- a/service/native/libs/libclat/clatutils_test.cpp
+++ b/service/native/libs/libclat/clatutils_test.cpp
@@ -19,6 +19,7 @@
#include <gtest/gtest.h>
#include <linux/if_packet.h>
#include <linux/if_tun.h>
+#include <netinet/in.h>
#include "tun_interface.h"
extern "C" {
@@ -182,6 +183,31 @@
v6Iface.destroy();
}
+// This is not a realistic test because we can't test generateIPv6Address here since it requires
+// manipulating routing, which we can't do without talking to the real netd on the system.
+// See test MakeChecksumNeutral.
+// TODO: remove this test once EthernetTetheringTest can test on mainline test coverage branch.
+TEST_F(ClatUtils, GenerateIpv6AddressFailWithUlaSocketAddress) {
+ // Create an interface for generateIpv6Address to connect to.
+ TunInterface tun;
+ ASSERT_EQ(0, tun.init());
+
+ // Unused because v6 address is ULA and makeChecksumNeutral has never called.
+ in_addr v4 = {inet_addr(kIPv4LocalAddr)};
+ // Not a NAT64 prefix because test can't connect to in generateIpv6Address.
+ // Used to be a fake address from the tun interface for generating an IPv6 socket address.
+ // nat64Prefix won't be used in MakeChecksumNeutral because MakeChecksumNeutral has never
+ // be called.
+ in6_addr nat64Prefix = tun.dstAddr(); // not realistic
+ in6_addr v6;
+ EXPECT_EQ(1, inet_pton(AF_INET6, "::", &v6)); // initialize as zero
+
+ EXPECT_EQ(-ENETUNREACH, generateIpv6Address(tun.name().c_str(), v4, nat64Prefix, &v6));
+ EXPECT_TRUE(IN6_IS_ADDR_ULA(&v6));
+
+ tun.destroy();
+}
+
} // namespace clat
} // namespace net
} // namespace android
diff --git a/service/proguard.flags b/service/proguard.flags
index 557ba59..94397ab 100644
--- a/service/proguard.flags
+++ b/service/proguard.flags
@@ -2,6 +2,8 @@
# TODO: instead of keeping everything, consider listing only "entry points"
# (service loader, JNI registered methods, etc) and letting the optimizer do its job
-keep class android.net.** { *; }
+-keep class com.android.connectivity.** { *; }
+-keep class com.android.net.** { *; }
-keep class !com.android.server.nearby.**,com.android.server.** { *; }
# Prevent proguard from stripping out any nearby-service and fast-pair-lite-protos fields.
@@ -13,4 +15,4 @@
# This replicates the base proguard rule used by the build by default
# (proguard_basic_keeps.flags), but needs to be specified here because the
# com.google.protobuf package is jarjared to the below package.
--keepclassmembers class * extends com.android.server.nearby.com.google.protobuf.MessageLite { <fields>; }
+-keepclassmembers class * extends com.android.connectivity.com.google.protobuf.MessageLite { <fields>; }
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 02b8e62..848901f 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -748,7 +748,7 @@
* The BPF program attached to the tc-police hook to account for to-be-dropped traffic.
*/
private static final String TC_POLICE_BPF_PROG_PATH =
- "/sys/fs/bpf/net_shared/prog_netd_schedact_ingress_account";
+ "/sys/fs/bpf/netd_shared/prog_netd_schedact_ingress_account";
private static String eventName(int what) {
return sMagicDecoderRing.get(what, Integer.toString(what));
@@ -3428,6 +3428,10 @@
for (NetworkAgentInfo nai : networksSortedById()) {
pw.println(nai.toString());
pw.increaseIndent();
+ pw.println("Nat464Xlat:");
+ pw.increaseIndent();
+ nai.dumpNat464Xlat(pw);
+ pw.decreaseIndent();
pw.println(String.format(
"Requests: REQUEST:%d LISTEN:%d BACKGROUND_REQUEST:%d total:%d",
nai.numForegroundNetworkRequests(),
@@ -3443,10 +3447,6 @@
pw.increaseIndent();
nai.dumpInactivityTimers(pw);
pw.decreaseIndent();
- pw.println("Nat464Xlat:");
- pw.increaseIndent();
- nai.dumpNat464Xlat(pw);
- pw.decreaseIndent();
pw.decreaseIndent();
}
}
@@ -3872,7 +3872,6 @@
}
final boolean wasValidated = nai.lastValidated;
- final boolean wasDefault = isDefaultNetwork(nai);
final boolean wasPartial = nai.partialConnectivity;
nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
final boolean partialConnectivityChanged =
@@ -7825,6 +7824,7 @@
}
nai.declaredCapabilities = new NetworkCapabilities(nc);
NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid,
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE),
mCarrierPrivilegeAuthenticator);
}
@@ -9251,7 +9251,18 @@
params.networkCapabilities = networkAgent.networkCapabilities;
params.linkProperties = new LinkProperties(networkAgent.linkProperties,
true /* parcelSensitiveFields */);
- networkAgent.networkMonitor().notifyNetworkConnected(params);
+ // isAtLeastT() is conservative here, as recent versions of NetworkStack support the
+ // newer callback even before T. However getInterfaceVersion is a synchronized binder
+ // call that would cause a Log.wtf to be emitted from the system_server process, and
+ // in the absence of a satisfactory, scalable solution which follows an easy/standard
+ // process to check the interface version, just use an SDK check. NetworkStack will
+ // always be new enough when running on T+.
+ if (SdkLevel.isAtLeastT()) {
+ networkAgent.networkMonitor().notifyNetworkConnected(params);
+ } else {
+ networkAgent.networkMonitor().notifyNetworkConnected(params.linkProperties,
+ params.networkCapabilities);
+ }
scheduleUnvalidatedPrompt(networkAgent);
// Whether a particular NetworkRequest listen should cause signal strength thresholds to
@@ -11348,6 +11359,9 @@
final int defaultRule;
switch (chain) {
case ConnectivityManager.FIREWALL_CHAIN_STANDBY:
+ case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1:
+ case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2:
+ case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3:
defaultRule = FIREWALL_RULE_ALLOW;
break;
case ConnectivityManager.FIREWALL_CHAIN_DOZABLE:
@@ -11397,6 +11411,15 @@
mBpfNetMaps.replaceUidChain("fw_low_power_standby", true /* isAllowList */,
uids);
break;
+ case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1:
+ mBpfNetMaps.replaceUidChain("fw_oem_deny_1", false /* isAllowList */, uids);
+ break;
+ case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2:
+ mBpfNetMaps.replaceUidChain("fw_oem_deny_2", false /* isAllowList */, uids);
+ break;
+ case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3:
+ mBpfNetMaps.replaceUidChain("fw_oem_deny_3", false /* isAllowList */, uids);
+ break;
default:
throw new IllegalArgumentException("replaceFirewallChain with invalid chain: "
+ chain);
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 4a7c77a..8cefd47 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -570,8 +570,14 @@
// Detect ipv4 mtu.
final Integer fwmark = getFwmark(netId);
- final int detectedMtu = mDeps.detectMtu(pfx96Str,
+ final int detectedMtu;
+ try {
+ detectedMtu = mDeps.detectMtu(pfx96Str,
ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
+ } catch (IOException e) {
+ tunFd.close();
+ throw new IOException("Detect MTU on " + tunIface + " failed: " + e);
+ }
final int mtu = adjustMtu(detectedMtu);
Log.i(TAG, "ipv4 mtu is " + mtu);
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index e8fc06d..e4ad391 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static com.android.net.module.util.CollectionUtils.contains;
@@ -127,6 +128,11 @@
final boolean supported = contains(NETWORK_TYPES, nai.networkInfo.getType());
final boolean connected = contains(NETWORK_STATES, nai.networkInfo.getState());
+ // Allow to run clat on test network.
+ // TODO: merge to boolean "supported" once boolean "supported" is migrated to
+ // NetworkCapabilities.TRANSPORT_*.
+ final boolean isTestNetwork = nai.networkCapabilities.hasTransport(TRANSPORT_TEST);
+
// Only run clat on networks that have a global IPv6 address and don't have a native IPv4
// address.
LinkProperties lp = nai.linkProperties;
@@ -137,8 +143,8 @@
final boolean skip464xlat = (nai.netAgentConfig() != null)
&& nai.netAgentConfig().skip464xlat;
- return supported && connected && isIpv6OnlyNetwork && !skip464xlat && !nai.destroyed
- && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
+ return (supported || isTestNetwork) && connected && isIpv6OnlyNetwork && !skip464xlat
+ && !nai.destroyed && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
? isCellular464XlatEnabled() : true);
}
@@ -540,7 +546,7 @@
mClatCoordinator.dump(pw);
pw.decreaseIndent();
} else {
- pw.println("<not start>");
+ pw.println("<not started>");
}
}
}
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 323888a..b40b6e0 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -19,6 +19,7 @@
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.transportNamesOf;
@@ -1224,20 +1225,22 @@
*
* @param nc the capabilities to sanitize
* @param creatorUid the UID of the process creating this network agent
+ * @param hasAutomotiveFeature true if this device has the automotive feature, false otherwise
* @param authenticator the carrier privilege authenticator to check for telephony constraints
*/
public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
- final int creatorUid, @NonNull final CarrierPrivilegeAuthenticator authenticator) {
+ final int creatorUid, final boolean hasAutomotiveFeature,
+ @Nullable final CarrierPrivilegeAuthenticator authenticator) {
if (nc.hasTransport(TRANSPORT_TEST)) {
nc.restrictCapabilitiesForTestNetwork(creatorUid);
}
- if (!areAllowedUidsAcceptableFromNetworkAgent(nc, authenticator)) {
+ if (!areAllowedUidsAcceptableFromNetworkAgent(nc, hasAutomotiveFeature, authenticator)) {
nc.setAllowedUids(new ArraySet<>());
}
}
private static boolean areAllowedUidsAcceptableFromNetworkAgent(
- @NonNull final NetworkCapabilities nc,
+ @NonNull final NetworkCapabilities nc, final boolean hasAutomotiveFeature,
@Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {
// NCs without access UIDs are fine.
if (!nc.hasAllowedUids()) return true;
@@ -1252,6 +1255,11 @@
// access UIDs
if (nc.hasTransport(TRANSPORT_TEST)) return true;
+ // Factories that make ethernet networks can allow UIDs for automotive devices.
+ if (nc.hasSingleTransport(TRANSPORT_ETHERNET) && hasAutomotiveFeature) {
+ return true;
+ }
+
// Factories that make cell networks can allow the UID for the carrier service package.
// This can only work in T where there is support for CarrierPrivilegeAuthenticator
if (null != carrierPrivilegeAuthenticator
@@ -1262,8 +1270,6 @@
return true;
}
- // TODO : accept Railway callers
-
return false;
}
diff --git a/service/src/com/android/server/net/DelayedDiskWrite.java b/service/src/com/android/server/net/DelayedDiskWrite.java
index 35dc455..41cb419 100644
--- a/service/src/com/android/server/net/DelayedDiskWrite.java
+++ b/service/src/com/android/server/net/DelayedDiskWrite.java
@@ -21,6 +21,8 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
@@ -32,11 +34,42 @@
public class DelayedDiskWrite {
private static final String TAG = "DelayedDiskWrite";
+ private final Dependencies mDeps;
+
private HandlerThread mDiskWriteHandlerThread;
private Handler mDiskWriteHandler;
/* Tracks multiple writes on the same thread */
private int mWriteSequence = 0;
+ public DelayedDiskWrite() {
+ this(new Dependencies());
+ }
+
+ @VisibleForTesting
+ DelayedDiskWrite(Dependencies deps) {
+ mDeps = deps;
+ }
+
+ /**
+ * Dependencies class of DelayedDiskWrite, used for injection in tests.
+ */
+ @VisibleForTesting
+ static class Dependencies {
+ /**
+ * Create a HandlerThread to use in DelayedDiskWrite.
+ */
+ public HandlerThread makeHandlerThread() {
+ return new HandlerThread("DelayedDiskWriteThread");
+ }
+
+ /**
+ * Quit the HandlerThread looper.
+ */
+ public void quitHandlerThread(HandlerThread handlerThread) {
+ handlerThread.getLooper().quit();
+ }
+ }
+
/**
* Used to do a delayed data write to a given {@link OutputStream}.
*/
@@ -65,7 +98,7 @@
/* Do a delayed write to disk on a separate handler thread */
synchronized (this) {
if (++mWriteSequence == 1) {
- mDiskWriteHandlerThread = new HandlerThread("DelayedDiskWriteThread");
+ mDiskWriteHandlerThread = mDeps.makeHandlerThread();
mDiskWriteHandlerThread.start();
mDiskWriteHandler = new Handler(mDiskWriteHandlerThread.getLooper());
}
@@ -99,9 +132,9 @@
// Quit if no more writes sent
synchronized (this) {
if (--mWriteSequence == 0) {
- mDiskWriteHandler.getLooper().quit();
- mDiskWriteHandler = null;
+ mDeps.quitHandlerThread(mDiskWriteHandlerThread);
mDiskWriteHandlerThread = null;
+ mDiskWriteHandler = null;
}
}
}
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index efea0f9..58731e0 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -23,7 +23,7 @@
java_library {
name: "FrameworksNetCommonTests",
- defaults: ["framework-connectivity-internal-test-defaults"],
+ defaults: ["framework-connectivity-test-defaults"],
srcs: [
"java/**/*.java",
"java/**/*.kt",
@@ -49,7 +49,6 @@
// jarjar stops at the first matching rule, so order of concatenation affects the output.
genrule {
name: "ConnectivityCoverageJarJarRules",
- defaults: ["jarjar-rules-combine-defaults"],
srcs: [
"tethering-jni-jarjar-rules.txt",
":connectivity-jarjar-rules",
@@ -57,6 +56,8 @@
":NetworkStackJarJarRules",
],
out: ["jarjar-rules-connectivity-coverage.txt"],
+ // Concat files with a line break in the middle
+ cmd: "for src in $(in); do cat $${src}; echo; done > $(out)",
visibility: ["//visibility:private"],
}
@@ -83,7 +84,7 @@
target_sdk_version: "31",
test_suites: ["general-tests", "mts-tethering"],
defaults: [
- "framework-connectivity-internal-test-defaults",
+ "framework-connectivity-test-defaults",
"FrameworksNetTests-jni-defaults",
"libnetworkstackutilsjni_deps",
],
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index b66a979..581ee22 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -46,6 +46,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk31;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -1307,6 +1308,7 @@
}
@Test @IgnoreUpTo(SC_V2)
+ @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden on T or above")
@DisableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
public void testExcludedRoutesDisabled() {
final LinkProperties lp = new LinkProperties();
diff --git a/tests/common/java/android/net/netstats/NetworkStatsHistoryTest.kt b/tests/common/java/android/net/netstats/NetworkStatsHistoryTest.kt
index c2654c5..a6c9f3c 100644
--- a/tests/common/java/android/net/netstats/NetworkStatsHistoryTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkStatsHistoryTest.kt
@@ -51,12 +51,37 @@
.build()
statsSingle.assertEntriesEqual(entry1)
assertEquals(DateUtils.HOUR_IN_MILLIS, statsSingle.bucketDuration)
+
val statsMultiple = NetworkStatsHistory
.Builder(DateUtils.SECOND_IN_MILLIS, /* initialCapacity */ 0)
.addEntry(entry1).addEntry(entry2).addEntry(entry3)
.build()
assertEquals(DateUtils.SECOND_IN_MILLIS, statsMultiple.bucketDuration)
- statsMultiple.assertEntriesEqual(entry1, entry2, entry3)
+ // Verify the entries exist and sorted.
+ statsMultiple.assertEntriesEqual(entry3, entry1, entry2)
+ }
+
+ @Test
+ fun testBuilderSortAndDeduplicate() {
+ val entry1 = NetworkStatsHistory.Entry(10, 30, 40, 4, 50, 5, 60)
+ val entry2 = NetworkStatsHistory.Entry(30, 15, 3, 41, 7, 1, 0)
+ val entry3 = NetworkStatsHistory.Entry(30, 999, 11, 14, 31, 2, 80)
+ val entry4 = NetworkStatsHistory.Entry(10, 15, 1, 17, 5, 33, 10)
+ val entry5 = NetworkStatsHistory.Entry(6, 1, 9, 11, 29, 1, 7)
+
+ // Entries for verification.
+ // Note that active time of 2 + 3 is truncated to bucket duration since the active time
+ // should not go over bucket duration.
+ val entry2and3 = NetworkStatsHistory.Entry(30, 1000, 14, 55, 38, 3, 80)
+ val entry1and4 = NetworkStatsHistory.Entry(10, 45, 41, 21, 55, 38, 70)
+
+ val statsMultiple = NetworkStatsHistory
+ .Builder(DateUtils.SECOND_IN_MILLIS, /* initialCapacity */ 0)
+ .addEntry(entry1).addEntry(entry2).addEntry(entry3)
+ .addEntry(entry4).addEntry(entry5).build()
+ assertEquals(DateUtils.SECOND_IN_MILLIS, statsMultiple.bucketDuration)
+ // Verify the entries sorted and deduplicated.
+ statsMultiple.assertEntriesEqual(entry5, entry1and4, entry2and3)
}
fun NetworkStatsHistory.assertEntriesEqual(vararg entries: NetworkStatsHistory.Entry) {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 524bd65..93e9dcd 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -217,7 +217,10 @@
Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
+ attempts + " attempts; sleeping "
+ SLEEP_TIME_SEC + " seconds before trying again");
- SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
+ // No sleep after the last turn
+ if (attempts <= maxAttempts) {
+ SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
+ }
} while (attempts <= maxAttempts);
assertEquals("Number of expected broadcasts for " + receiverName + " not reached after "
+ maxAttempts * SLEEP_TIME_SEC + " seconds", expectedCount, count);
@@ -328,7 +331,10 @@
}
Log.d(TAG, "App not on background state (" + state + ") on attempt #" + i
+ "; sleeping 1s before trying again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("App2 (" + mUid + ") is not on background state after "
+ maxTries + " attempts: " + state);
@@ -347,7 +353,10 @@
Log.d(TAG, "App not on foreground state on attempt #" + i
+ "; sleeping 1s before trying again");
turnScreenOn();
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("App2 (" + mUid + ") is not on foreground state after "
+ maxTries + " attempts: " + state);
@@ -365,7 +374,10 @@
}
Log.d(TAG, "App not on foreground service state on attempt #" + i
+ "; sleeping 1s before trying again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("App2 (" + mUid + ") is not on foreground service state after "
+ maxTries + " attempts: " + state);
@@ -506,7 +518,10 @@
Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
+ checker.getExpected() + "' on attempt #" + i
+ "; sleeping " + napTimeSeconds + "s before trying again");
- SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
+ }
}
fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after "
+ maxTries
@@ -578,7 +593,10 @@
}
Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
+ expected + ", got " + actual + "); sleeping 1s before polling again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
+ ". Full list: " + uids);
@@ -738,7 +756,8 @@
protected void assertAppIdle(boolean enabled) throws Exception {
try {
- assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 15, 2, "Idle=" + enabled);
+ assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG,
+ 30 /* maxTries */, 1 /* napTimeSeconds */, "Idle=" + enabled);
} catch (Throwable e) {
throw e;
}
@@ -765,7 +784,10 @@
return;
}
Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again");
- SystemClock.sleep(SECOND_IN_MS);
+ // No sleep after the last turn
+ if (i < maxTries) {
+ SystemClock.sleep(SECOND_IN_MS);
+ }
}
fail("app2 receiver is not ready in " + mUid);
}
@@ -814,8 +836,6 @@
return;
} else if (type == TYPE_COMPONENT_ACTIVTIY) {
turnScreenOn();
- // Wait for screen-on state to propagate through the system.
- SystemClock.sleep(2000);
final CountDownLatch latch = new CountDownLatch(1);
final Intent launchIntent = getIntentForComponent(type);
final Bundle extras = new Bundle();
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
index 51acfdf..82f13ae 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
@@ -29,6 +29,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
+import android.view.WindowManager;
import androidx.annotation.GuardedBy;
@@ -46,6 +47,8 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "MyActivity.onCreate()");
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@Override
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index e979a3b..a6ed762 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -62,6 +62,7 @@
// sdk_version: "current",
platform_apis: true,
required: ["ConnectivityChecker"],
+ test_config_template: "AndroidTestTemplate.xml",
}
// Networking CTS tests for development and release. These tests always target the platform SDK
@@ -79,7 +80,16 @@
"cts",
"general-tests",
],
- test_config_template: "AndroidTestTemplate.xml",
+}
+
+java_defaults {
+ name: "CtsNetTestCasesApiStableDefaults",
+ // TODO: CTS should not depend on the entirety of the networkstack code.
+ static_libs: [
+ "NetworkStackApiStableLib",
+ ],
+ jni_uses_sdk_apis: true,
+ min_sdk_version: "29",
}
// Networking CTS tests that target the latest released SDK. These tests can be installed on release
@@ -87,14 +97,11 @@
// on release devices.
android_test {
name: "CtsNetTestCasesLatestSdk",
- defaults: ["CtsNetTestCasesDefaults"],
- // TODO: CTS should not depend on the entirety of the networkstack code.
- static_libs: [
- "NetworkStackApiStableLib",
+ defaults: [
+ "CtsNetTestCasesDefaults",
+ "CtsNetTestCasesApiStableDefaults",
],
- jni_uses_sdk_apis: true,
- min_sdk_version: "29",
- target_sdk_version: "30",
+ target_sdk_version: "33",
test_suites: [
"general-tests",
"mts-dnsresolver",
@@ -102,5 +109,21 @@
"mts-tethering",
"mts-wifi",
],
- test_config_template: "AndroidTestTemplate.xml",
}
+
+android_test {
+ name: "CtsNetTestCasesMaxTargetSdk31", // Must match CtsNetTestCasesMaxTargetSdk31 annotation.
+ defaults: [
+ "CtsNetTestCasesDefaults",
+ "CtsNetTestCasesApiStableDefaults",
+ ],
+ target_sdk_version: "31",
+ package_name: "android.net.cts.maxtargetsdk31", // CTS package names must be unique.
+ instrumentation_target_package: "android.net.cts.maxtargetsdk31",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-networking",
+ ],
+}
+
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 33f3af5..d2fb04a 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -33,10 +33,21 @@
<target_preparer class="com.android.testutils.DisableConfigSyncTargetPreparer">
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.net.cts" />
+ <option name="package" value="{PACKAGE}" />
<option name="runtime-hint" value="9m4s" />
<option name="hidden-api-checks" value="false" />
<option name="isolated-storage" value="false" />
+ <!-- Test filter that allows test APKs to select which tests they want to run by annotating
+ those tests with an annotation matching the name of the APK.
+
+ This allows us to maintain one AndroidTestTemplate.xml for all CtsNetTestCases*.apk,
+ and have CtsNetTestCases and CtsNetTestCasesLatestSdk run all tests, but have
+ CtsNetTestCasesMaxTargetSdk31 run only tests that require target SDK 31.
+
+ This relies on the fact that if the class specified in include-annotation exists, then
+ the runner will only run the tests annotated with that annotation, but if it does not,
+ the runner will run all the tests. -->
+ <option name="include-annotation" value="com.android.testutils.filters.{MODULE}" />
</test>
<!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
one of the Mainline modules below is present on the device used for testing. -->
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
index 5b37294..9b81a56 100644
--- a/tests/cts/net/api23Test/Android.bp
+++ b/tests/cts/net/api23Test/Android.bp
@@ -51,5 +51,8 @@
"cts",
"general-tests",
],
-
+ data: [
+ ":CtsNetTestAppForApi23",
+ ],
+ per_testcase_directory: true,
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 8f6786f..08cf0d7 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -37,6 +37,11 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.EXTRA_NETWORK;
import static android.net.ConnectivityManager.EXTRA_NETWORK_REQUEST;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
@@ -204,6 +209,8 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
import java.net.HttpURLConnection;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -218,6 +225,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Objects;
+import java.util.Random;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
@@ -255,6 +263,7 @@
private static final int NETWORK_CALLBACK_TIMEOUT_MS = 30_000;
private static final int LISTEN_ACTIVITY_TIMEOUT_MS = 5_000;
private static final int NO_CALLBACK_TIMEOUT_MS = 100;
+ private static final int SOCKET_TIMEOUT_MS = 100;
private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500;
// device could have only one interface: data, wifi.
@@ -3235,14 +3244,16 @@
// TODD: Have a significant signal to know the uids has been sent to netd.
assertBindSocketToNetworkSuccess(network);
- // Uid is in allowed list. Try file network request again.
- requestNetwork(restrictedRequest, restrictedNetworkCb);
- // Verify that the network is restricted.
- restrictedNetworkCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
- NETWORK_CALLBACK_TIMEOUT_MS,
- entry -> network.equals(entry.getNetwork())
- && (!((CallbackEntry.CapabilitiesChanged) entry).getCaps()
- .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)));
+ if (TestUtils.shouldTestTApis()) {
+ // Uid is in allowed list. Try file network request again.
+ requestNetwork(restrictedRequest, restrictedNetworkCb);
+ // Verify that the network is restricted.
+ restrictedNetworkCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+ NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> network.equals(entry.getNetwork())
+ && (!((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)));
+ }
} finally {
agent.unregister();
@@ -3272,6 +3283,112 @@
assertTrue(dumpOutput, dumpOutput.contains("BPF map content"));
}
+ private void checkFirewallBlocking(final DatagramSocket srcSock, final DatagramSocket dstSock,
+ final boolean expectBlock) throws Exception {
+ final Random random = new Random();
+ final byte[] sendData = new byte[100];
+ random.nextBytes(sendData);
+
+ final DatagramPacket pkt = new DatagramPacket(sendData, sendData.length,
+ InetAddresses.parseNumericAddress("::1"), dstSock.getLocalPort());
+ try {
+ srcSock.send(pkt);
+ } catch (IOException e) {
+ if (expectBlock) {
+ return;
+ }
+ fail("Expect not to be blocked by firewall but sending packet was blocked");
+ }
+
+ if (expectBlock) {
+ fail("Expect to be blocked by firewall but sending packet was not blocked");
+ }
+
+ dstSock.receive(pkt);
+ assertArrayEquals(sendData, pkt.getData());
+ }
+
+ private static final boolean EXPECT_PASS = false;
+ private static final boolean EXPECT_BLOCK = true;
+
+ private void doTestFirewallBlockingDenyRule(final int chain) {
+ runWithShellPermissionIdentity(() -> {
+ try (DatagramSocket srcSock = new DatagramSocket();
+ DatagramSocket dstSock = new DatagramSocket()) {
+ dstSock.setSoTimeout(SOCKET_TIMEOUT_MS);
+
+ // No global config, No uid config
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+ // Has global config, No uid config
+ mCm.setFirewallChainEnabled(chain, true /* enable */);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+ // Has global config, Has uid config
+ mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_DENY);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_BLOCK);
+
+ // No global config, Has uid config
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+ // No global config, No uid config
+ mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_ALLOW);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+ } finally {
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_ALLOW);
+ }
+ }, NETWORK_SETTINGS);
+ }
+
+ private void doTestFirewallBlockingAllowRule(final int chain) {
+ runWithShellPermissionIdentity(() -> {
+ try (DatagramSocket srcSock = new DatagramSocket();
+ DatagramSocket dstSock = new DatagramSocket()) {
+ dstSock.setSoTimeout(SOCKET_TIMEOUT_MS);
+
+ // No global config, No uid config
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+ // Has global config, No uid config
+ mCm.setFirewallChainEnabled(chain, true /* enable */);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_BLOCK);
+
+ // Has global config, Has uid config
+ mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_ALLOW);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+ // No global config, Has uid config
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+ // No global config, No uid config
+ mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_DENY);
+ checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+ } finally {
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_DENY);
+ }
+ }, NETWORK_SETTINGS);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testFirewallBlocking() {
+ // Following tests affect the actual state of networking on the device after the test.
+ // This might cause unexpected behaviour of the device. So, we skip them for now.
+ // We will enable following tests after adding the logic of firewall state restoring.
+ // doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_DOZABLE);
+ // doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_POWERSAVE);
+ // doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_RESTRICTED);
+ // doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_LOW_POWER_STANDBY);
+
+ // doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_STANDBY);
+ doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_OEM_DENY_1);
+ doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_OEM_DENY_2);
+ doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_OEM_DENY_3);
+ }
+
private void assumeTestSApis() {
// Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
// shims, and @IgnoreUpTo does not check that.
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 04434e5..db24b44 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -15,67 +15,98 @@
*/
package android.net.cts
+import android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
import android.Manifest.permission.MANAGE_TEST_NETWORKS
import android.Manifest.permission.NETWORK_SETTINGS
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.EthernetManager
+import android.net.EthernetManager.InterfaceStateListener
+import android.net.EthernetManager.ROLE_CLIENT
+import android.net.EthernetManager.ROLE_NONE
+import android.net.EthernetManager.ROLE_SERVER
+import android.net.EthernetManager.STATE_ABSENT
+import android.net.EthernetManager.STATE_LINK_DOWN
+import android.net.EthernetManager.STATE_LINK_UP
+import android.net.EthernetManager.TetheredInterfaceCallback
+import android.net.EthernetManager.TetheredInterfaceRequest
+import android.net.EthernetNetworkSpecifier
import android.net.InetAddresses
import android.net.IpConfiguration
import android.net.MacAddress
+import android.net.Network
+import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_TEST
+import android.net.NetworkRequest
import android.net.TestNetworkInterface
import android.net.TestNetworkManager
-import android.platform.test.annotations.AppModeFull
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.runner.AndroidJUnit4
-import com.android.net.module.util.ArrayTrackRecord
-import com.android.net.module.util.TrackRecord
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.SC_V2
-import com.android.testutils.runAsShell
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import android.content.Context
-import org.junit.runner.RunWith
-import kotlin.test.assertNull
-import kotlin.test.fail
import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged
+import android.os.Build
import android.os.Handler
import android.os.HandlerExecutor
import android.os.Looper
-import com.android.networkstack.apishim.common.EthernetManagerShim.InterfaceStateListener
-import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_ABSENT
-import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_DOWN
-import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_UP
-import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_CLIENT
-import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_NONE
-import com.android.networkstack.apishim.EthernetManagerShimImpl
+import android.os.SystemProperties
+import android.platform.test.annotations.AppModeFull
+import android.util.ArraySet
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.net.module.util.TrackRecord
+import com.android.testutils.anyNetwork
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.RouterAdvertisementResponder
import com.android.testutils.TapPacketReader
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.runAsShell
import com.android.testutils.waitForIdle
+import org.junit.After
+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.Executor
-import kotlin.test.assertFalse
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
import kotlin.test.assertTrue
-import java.net.NetworkInterface
+import kotlin.test.fail
-private const val TIMEOUT_MS = 1000L
+// TODO: try to lower this timeout in the future. Currently, ethernet tests are still flaky because
+// the interface is not ready fast enough (mostly due to the up / up / down / up issue).
+private const val TIMEOUT_MS = 2000L
private const val NO_CALLBACK_TIMEOUT_MS = 200L
private val DEFAULT_IP_CONFIGURATION = IpConfiguration(IpConfiguration.IpAssignment.DHCP,
IpConfiguration.ProxySettings.NONE, null, null)
+private val ETH_REQUEST: NetworkRequest = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_ETHERNET)
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .build()
@AppModeFull(reason = "Instant apps can't access EthernetManager")
-@RunWith(AndroidJUnit4::class)
+// EthernetManager is not updatable before T, so tests do not need to be backwards compatible.
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class EthernetManagerTest {
- // EthernetManager is not updatable before T, so tests do not need to be backwards compatible
- @get:Rule
- val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
- private val em by lazy { EthernetManagerShimImpl.newInstance(context) }
+ private val em by lazy { context.getSystemService(EthernetManager::class.java) }
+ private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
+ private val ifaceListener = EthernetStateListener()
private val createdIfaces = ArrayList<EthernetTestInterface>()
private val addedListeners = ArrayList<EthernetStateListener>()
+ private val networkRequests = ArrayList<TestableNetworkCallback>()
+
+ private var tetheredInterfaceRequest: TetheredInterfaceRequest? = null
private class EthernetTestInterface(
context: Context,
@@ -91,7 +122,7 @@
val tnm = context.getSystemService(TestNetworkManager::class.java)
tnm.createTapInterface(false /* bringUp */)
}
- val mtu = NetworkInterface.getByName(tapInterface.interfaceName).getMTU()
+ val mtu = 1500
packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu)
raResponder = RouterAdvertisementResponder(packetReader)
raResponder.addRouterEntry(MacAddress.fromString("01:23:45:67:89:ab"),
@@ -141,23 +172,65 @@
}
fun expectCallback(iface: EthernetTestInterface, state: Int, role: Int) {
- expectCallback(InterfaceStateChanged(iface.interfaceName, state, role,
- if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null))
+ expectCallback(createChangeEvent(iface.interfaceName, state, role))
}
+ fun createChangeEvent(iface: String, state: Int, role: Int) =
+ InterfaceStateChanged(iface, state, role,
+ if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null)
+
fun pollForNextCallback(): CallbackEntry {
return events.poll(TIMEOUT_MS) ?: fail("Did not receive callback after ${TIMEOUT_MS}ms")
}
+ 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.interfaceName, state, role)
+ }
+
fun assertNoCallback() {
val cb = events.poll(NO_CALLBACK_TIMEOUT_MS)
assertNull(cb, "Expected no callback but got $cb")
}
}
+ private class TetheredInterfaceListener : TetheredInterfaceCallback {
+ private val available = CompletableFuture<String>()
+
+ override fun onAvailable(iface: String) {
+ available.complete(iface)
+ }
+
+ override fun onUnavailable() {
+ available.completeExceptionally(IllegalStateException("onUnavailable was called"))
+ }
+
+ fun expectOnAvailable(): String {
+ return available.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+
+ fun expectOnUnavailable() {
+ // Assert that the future fails with the IllegalStateException from the
+ // completeExceptionally() call inside onUnavailable.
+ assertFailsWith(IllegalStateException::class) {
+ try {
+ available.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ } catch (e: ExecutionException) {
+ throw e.cause!!
+ }
+ }
+ }
+ }
+
@Before
fun setUp() {
setIncludeTestInterfaces(true)
+ addInterfaceStateListener(ifaceListener)
}
@After
@@ -165,22 +238,33 @@
setIncludeTestInterfaces(false)
for (iface in createdIfaces) {
iface.destroy()
+ ifaceListener.eventuallyExpect(iface, STATE_ABSENT, ROLE_NONE)
}
for (listener in addedListeners) {
em.removeInterfaceStateListener(listener)
}
+ networkRequests.forEach { cm.unregisterNetworkCallback(it) }
+ releaseTetheredInterface()
}
- private fun addInterfaceStateListener(executor: Executor, listener: EthernetStateListener) {
- em.addInterfaceStateListener(executor, listener)
+ private fun addInterfaceStateListener(listener: EthernetStateListener) {
+ runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) {
+ em.addInterfaceStateListener(HandlerExecutor(Handler(Looper.getMainLooper())), listener)
+ }
addedListeners.add(listener)
}
private fun createInterface(): EthernetTestInterface {
- return EthernetTestInterface(
+ val iface = EthernetTestInterface(
context,
Handler(Looper.getMainLooper())
).also { createdIfaces.add(it) }
+ with(ifaceListener) {
+ // when an interface comes up, we should always see a down cb before an up cb.
+ eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+ expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
+ }
+ return iface
}
private fun setIncludeTestInterfaces(value: Boolean) {
@@ -192,49 +276,141 @@
private fun removeInterface(iface: EthernetTestInterface) {
iface.destroy()
createdIfaces.remove(iface)
+ ifaceListener.eventuallyExpect(iface, STATE_ABSENT, ROLE_NONE)
}
- @Test
- public fun testCallbacks() {
- val executor = HandlerExecutor(Handler(Looper.getMainLooper()))
+ private fun requestNetwork(request: NetworkRequest): TestableNetworkCallback {
+ return TestableNetworkCallback().also {
+ cm.requestNetwork(request, it)
+ networkRequests.add(it)
+ }
+ }
+ private fun releaseNetwork(cb: TestableNetworkCallback) {
+ cm.unregisterNetworkCallback(cb)
+ networkRequests.remove(cb)
+ }
+
+ private fun requestTetheredInterface() = TetheredInterfaceListener().also {
+ tetheredInterfaceRequest = runAsShell(NETWORK_SETTINGS) {
+ em.requestTetheredInterface(HandlerExecutor(Handler(Looper.getMainLooper())), it)
+ }
+ }
+
+ private fun releaseTetheredInterface() {
+ runAsShell(NETWORK_SETTINGS) {
+ tetheredInterfaceRequest?.release()
+ tetheredInterfaceRequest = null
+ }
+ }
+
+ private fun NetworkRequest.createCopyWithEthernetSpecifier(ifaceName: String) =
+ NetworkRequest.Builder(NetworkRequest(ETH_REQUEST))
+ .setNetworkSpecifier(EthernetNetworkSpecifier(ifaceName)).build()
+
+ // It can take multiple seconds for the network to become available.
+ private fun TestableNetworkCallback.expectAvailable() =
+ expectCallback<Available>(anyNetwork(), 5000/*ms timeout*/).network
+
+ // b/233534110: eventuallyExpect<Lost>() does not advance ReadHead, use
+ // eventuallyExpect(Lost::class) instead.
+ private fun TestableNetworkCallback.eventuallyExpectLost(n: Network? = null) =
+ eventuallyExpect(Lost::class, TIMEOUT_MS) { n?.equals(it.network) ?: true }
+
+ private fun TestableNetworkCallback.assertNotLost(n: Network? = null) =
+ assertNoCallbackThat() { it is Lost && (n?.equals(it.network) ?: true) }
+
+ @Test
+ fun testCallbacks() {
// If an interface exists when the callback is registered, it is reported on registration.
val iface = createInterface()
- val listener = EthernetStateListener()
- addInterfaceStateListener(executor, listener)
- listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
+ val listener1 = EthernetStateListener()
+ addInterfaceStateListener(listener1)
+ validateListenerOnRegistration(listener1)
// If an interface appears, existing callbacks see it.
// TODO: fix the up/up/down/up callbacks and only send down/up.
val iface2 = createInterface()
- listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
// Register a new listener, it should see state of all existing interfaces immediately.
val listener2 = EthernetStateListener()
- addInterfaceStateListener(executor, listener2)
- listener2.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
- listener2.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ addInterfaceStateListener(listener2)
+ validateListenerOnRegistration(listener2)
// Removing interfaces first sends link down, then STATE_ABSENT/ROLE_NONE.
removeInterface(iface)
- for (listener in addedListeners) {
+ for (listener in listOf(listener1, listener2)) {
listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
}
removeInterface(iface2)
- for (listener in addedListeners) {
+ for (listener in listOf(listener1, listener2)) {
listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
listener.expectCallback(iface2, STATE_ABSENT, ROLE_NONE)
listener.assertNoCallback()
}
}
+ // 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)
+ }
+
@Test
- public fun testGetInterfaceList() {
+ fun testCallbacks_forServerModeInterfaces() {
+ // do not run this test when adb might be connected over ethernet.
+ assumeFalse(isAdbOverNetwork())
+
+ 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)
+
+ releaseTetheredInterface()
+ listener.eventuallyExpect(ifaceName, STATE_LINK_UP, ROLE_CLIENT)
+ }
+
+ /**
+ * Validate all interfaces are returned for an EthernetStateListener upon registration.
+ */
+ private fun validateListenerOnRegistration(listener: EthernetStateListener) {
+ // Get all tracked interfaces to validate on listener registration. Ordering and interface
+ // state (up/down) can't be validated for interfaces not created as part of testing.
+ val ifaces = em.getInterfaceList()
+ val polledIfaces = ArraySet<String>()
+ for (i in ifaces) {
+ val event = (listener.pollForNextCallback() as InterfaceStateChanged)
+ val iface = event.iface
+ assertTrue(polledIfaces.add(iface), "Duplicate interface $iface returned")
+ assertTrue(ifaces.contains(iface), "Untracked interface $iface returned")
+ // If the event's iface was created in the test, additional criteria can be validated.
+ createdIfaces.find { it.interfaceName.equals(iface) }?.let {
+ assertEquals(event,
+ listener.createChangeEvent(it.interfaceName,
+ STATE_LINK_UP,
+ ROLE_CLIENT))
+ }
+ }
+ // Assert all callbacks are accounted for.
+ listener.assertNoCallback()
+ }
+
+ @Test
+ fun testGetInterfaceList() {
setIncludeTestInterfaces(true)
// Create two test interfaces and check the return list contains the interface names.
@@ -254,4 +430,105 @@
removeInterface(iface2)
}
+
+ @Test
+ fun testNetworkRequest_withSingleExistingInterface() {
+ setIncludeTestInterfaces(true)
+ createInterface()
+
+ // install a listener which will later be used to verify the Lost callback
+ val listenerCb = TestableNetworkCallback()
+ cm.registerNetworkCallback(ETH_REQUEST, listenerCb)
+ networkRequests.add(listenerCb)
+
+ val cb = requestNetwork(ETH_REQUEST)
+ val network = cb.expectAvailable()
+
+ cb.assertNotLost()
+ releaseNetwork(cb)
+ listenerCb.eventuallyExpectLost(network)
+ }
+
+ @Test
+ fun testNetworkRequest_beforeSingleInterfaceIsUp() {
+ setIncludeTestInterfaces(true)
+
+ val cb = requestNetwork(ETH_REQUEST)
+
+ // bring up interface after network has been requested
+ val iface = createInterface()
+ val network = cb.expectAvailable()
+
+ // remove interface before network request has been removed
+ cb.assertNotLost()
+ removeInterface(iface)
+ cb.eventuallyExpectLost()
+
+ releaseNetwork(cb)
+ }
+
+ @Test
+ fun testNetworkRequest_withMultipleInterfaces() {
+ setIncludeTestInterfaces(true)
+
+ val iface1 = createInterface()
+ val iface2 = createInterface()
+
+ val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName))
+
+ val network = cb.expectAvailable()
+ cb.expectCapabilitiesThat(network) {
+ it.networkSpecifier == EthernetNetworkSpecifier(iface2.interfaceName)
+ }
+
+ removeInterface(iface1)
+ cb.assertNotLost()
+ removeInterface(iface2)
+ cb.eventuallyExpectLost()
+
+ releaseNetwork(cb)
+ }
+
+ @Test
+ fun testNetworkRequest_withInterfaceBeingReplaced() {
+ setIncludeTestInterfaces(true)
+ val iface1 = createInterface()
+
+ val cb = requestNetwork(ETH_REQUEST)
+ val network = cb.expectAvailable()
+
+ // create another network and verify the request sticks to the current network
+ val iface2 = createInterface()
+ cb.assertNotLost()
+
+ // remove iface1 and verify the request brings up iface2
+ removeInterface(iface1)
+ cb.eventuallyExpectLost(network)
+ val network2 = cb.expectAvailable()
+
+ releaseNetwork(cb)
+ }
+
+ @Test
+ fun testNetworkRequest_withMultipleInterfacesAndRequests() {
+ setIncludeTestInterfaces(true)
+ val iface1 = createInterface()
+ val iface2 = createInterface()
+
+ val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.interfaceName))
+ val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName))
+ val cb3 = requestNetwork(ETH_REQUEST)
+
+ cb1.expectAvailable()
+ cb2.expectAvailable()
+ cb3.expectAvailable()
+
+ cb1.assertNotLost()
+ cb2.assertNotLost()
+ cb3.assertNotLost()
+
+ releaseNetwork(cb1)
+ releaseNetwork(cb2)
+ releaseNetwork(cb3)
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/EthernetNetworkUpdateRequestTest.java b/tests/cts/net/src/android/net/cts/EthernetNetworkUpdateRequestTest.java
new file mode 100644
index 0000000..c8ee0c7
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/EthernetNetworkUpdateRequestTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.net.EthernetNetworkUpdateRequest;
+import android.net.IpConfiguration;
+import android.net.NetworkCapabilities;
+import android.net.StaticIpConfiguration;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+@RunWith(DevSdkIgnoreRunner.class)
+public class EthernetNetworkUpdateRequestTest {
+ private static final NetworkCapabilities DEFAULT_CAPS =
+ new NetworkCapabilities.Builder()
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED).build();
+ private static final StaticIpConfiguration DEFAULT_STATIC_IP_CONFIG =
+ new StaticIpConfiguration.Builder().setDomains("test").build();
+ private static final IpConfiguration DEFAULT_IP_CONFIG =
+ new IpConfiguration.Builder()
+ .setStaticIpConfiguration(DEFAULT_STATIC_IP_CONFIG).build();
+
+ private EthernetNetworkUpdateRequest createRequest(@NonNull final NetworkCapabilities nc,
+ @NonNull final IpConfiguration ipConfig) {
+ return new EthernetNetworkUpdateRequest.Builder()
+ .setNetworkCapabilities(nc)
+ .setIpConfiguration(ipConfig)
+ .build();
+ }
+
+ @Test
+ public void testGetNetworkCapabilities() {
+ final EthernetNetworkUpdateRequest r = createRequest(DEFAULT_CAPS, DEFAULT_IP_CONFIG);
+ assertEquals(DEFAULT_CAPS, r.getNetworkCapabilities());
+ }
+
+ @Test
+ public void testGetIpConfiguration() {
+ final EthernetNetworkUpdateRequest r = createRequest(DEFAULT_CAPS, DEFAULT_IP_CONFIG);
+ assertEquals(DEFAULT_IP_CONFIG, r.getIpConfiguration());
+ }
+
+ @Test
+ public void testBuilderWithRequest() {
+ final EthernetNetworkUpdateRequest r = createRequest(DEFAULT_CAPS, DEFAULT_IP_CONFIG);
+ final EthernetNetworkUpdateRequest rFromExisting =
+ new EthernetNetworkUpdateRequest.Builder(r).build();
+
+ assertNotSame(r, rFromExisting);
+ assertEquals(r.getIpConfiguration(), rFromExisting.getIpConfiguration());
+ assertEquals(r.getNetworkCapabilities(), rFromExisting.getNetworkCapabilities());
+ }
+
+ @Test
+ public void testNullIpConfigurationAndNetworkCapabilitiesThrows() {
+ assertThrows("Should not be able to build with null ip config and network capabilities.",
+ IllegalStateException.class,
+ () -> createRequest(null, null));
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 6c5b792..64cc97d 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -22,6 +22,7 @@
import android.net.Network
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
import android.net.NetworkCapabilities.TRANSPORT_TEST
import android.net.NetworkRequest
@@ -45,16 +46,16 @@
import android.net.nsd.NsdManager.RegistrationListener
import android.net.nsd.NsdManager.ResolveListener
import android.net.nsd.NsdServiceInfo
+import android.os.Handler
import android.os.HandlerThread
+import android.os.Process.myTid
import android.platform.test.annotations.AppModeFull
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.TrackRecord
-import com.android.networkstack.apishim.ConstantsShim
import com.android.networkstack.apishim.NsdShimImpl
-import com.android.testutils.SC_V2
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.runAsShell
@@ -113,12 +114,20 @@
private interface NsdEvent
private open class NsdRecord<T : NsdEvent> private constructor(
- private val history: ArrayTrackRecord<T>
+ private val history: ArrayTrackRecord<T>,
+ private val expectedThreadId: Int? = null
) : TrackRecord<T> by history {
- constructor() : this(ArrayTrackRecord())
+ constructor(expectedThreadId: Int? = null) : this(ArrayTrackRecord(), expectedThreadId)
val nextEvents = history.newReadHead()
+ override fun add(e: T): Boolean {
+ if (expectedThreadId != null) {
+ assertEquals(expectedThreadId, myTid(), "Callback is running on the wrong thread")
+ }
+ return history.add(e)
+ }
+
inline fun <reified V : NsdEvent> expectCallbackEventually(
crossinline predicate: (V) -> Boolean = { true }
): V = nextEvents.poll(TIMEOUT_MS) { e -> e is V && predicate(e) } as V?
@@ -138,8 +147,8 @@
}
}
- private class NsdRegistrationRecord : RegistrationListener,
- NsdRecord<NsdRegistrationRecord.RegistrationEvent>() {
+ private class NsdRegistrationRecord(expectedThreadId: Int? = null) : RegistrationListener,
+ NsdRecord<NsdRegistrationRecord.RegistrationEvent>(expectedThreadId) {
sealed class RegistrationEvent : NsdEvent {
abstract val serviceInfo: NsdServiceInfo
@@ -176,8 +185,8 @@
}
}
- private class NsdDiscoveryRecord : DiscoveryListener,
- NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>() {
+ private class NsdDiscoveryRecord(expectedThreadId: Int? = null) :
+ DiscoveryListener, NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>(expectedThreadId) {
sealed class DiscoveryEvent : NsdEvent {
data class StartDiscoveryFailed(val serviceType: String, val errorCode: Int)
: DiscoveryEvent()
@@ -249,9 +258,11 @@
fun setUp() {
handlerThread.start()
- runAsShell(MANAGE_TEST_NETWORKS) {
- testNetwork1 = createTestNetwork()
- testNetwork2 = createTestNetwork()
+ if (TestUtils.shouldTestTApis()) {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1 = createTestNetwork()
+ testNetwork2 = createTestNetwork()
+ }
}
}
@@ -290,9 +301,11 @@
@After
fun tearDown() {
- runAsShell(MANAGE_TEST_NETWORKS) {
- testNetwork1.close(cm)
- testNetwork2.close(cm)
+ if (TestUtils.shouldTestTApis()) {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1.close(cm)
+ testNetwork2.close(cm)
+ }
}
handlerThread.quitSafely()
}
@@ -419,7 +432,7 @@
@Test
fun testNsdManager_DiscoverOnNetwork() {
// This test requires shims supporting T+ APIs (discovering on specific network)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -453,16 +466,19 @@
@Test
fun testNsdManager_DiscoverWithNetworkRequest() {
// This test requires shims supporting T+ APIs (discovering on network request)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
si.serviceName = this.serviceName
si.port = 12345 // Test won't try to connect so port does not matter
- val registrationRecord = NsdRegistrationRecord()
- val registeredInfo1 = registerService(registrationRecord, si)
- val discoveryRecord = NsdDiscoveryRecord()
+ val handler = Handler(handlerThread.looper)
+ val executor = Executor { handler.post(it) }
+
+ val registrationRecord = NsdRegistrationRecord(expectedThreadId = handlerThread.threadId)
+ val registeredInfo1 = registerService(registrationRecord, si, executor)
+ val discoveryRecord = NsdDiscoveryRecord(expectedThreadId = handlerThread.threadId)
tryTest {
val specifier = TestNetworkSpecifier(testNetwork1.iface.interfaceName)
@@ -472,7 +488,7 @@
.addTransportType(TRANSPORT_TEST)
.setNetworkSpecifier(specifier)
.build(),
- Executor { it.run() }, discoveryRecord)
+ executor, discoveryRecord)
val discoveryStarted = discoveryRecord.expectCallback<DiscoveryStarted>()
assertEquals(SERVICE_TYPE, discoveryStarted.serviceType)
@@ -488,7 +504,7 @@
assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceLost1.serviceInfo))
registrationRecord.expectCallback<ServiceUnregistered>()
- val registeredInfo2 = registerService(registrationRecord, si)
+ val registeredInfo2 = registerService(registrationRecord, si, executor)
val serviceDiscovered2 = discoveryRecord.expectCallback<ServiceFound>()
assertEquals(registeredInfo2.serviceName, serviceDiscovered2.serviceInfo.serviceName)
assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceDiscovered2.serviceInfo))
@@ -516,9 +532,42 @@
}
@Test
+ fun testNsdManager_DiscoverWithNetworkRequest_NoMatchingNetwork() {
+ // This test requires shims supporting T+ APIs (discovering on network request)
+ assumeTrue(TestUtils.shouldTestTApis())
+
+ val si = NsdServiceInfo()
+ si.serviceType = SERVICE_TYPE
+ si.serviceName = this.serviceName
+ si.port = 12345 // Test won't try to connect so port does not matter
+
+ val handler = Handler(handlerThread.looper)
+ val executor = Executor { handler.post(it) }
+
+ val discoveryRecord = NsdDiscoveryRecord(expectedThreadId = handlerThread.threadId)
+ val specifier = TestNetworkSpecifier(testNetwork1.iface.interfaceName)
+
+ tryTest {
+ nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+ NetworkRequest.Builder()
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .addTransportType(TRANSPORT_TEST)
+ // Specified network does not have this capability
+ .addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ .setNetworkSpecifier(specifier)
+ .build(),
+ executor, discoveryRecord)
+ discoveryRecord.expectCallback<DiscoveryStarted>()
+ } cleanup {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+ discoveryRecord.expectCallback<DiscoveryStopped>()
+ }
+ }
+
+ @Test
fun testNsdManager_ResolveOnNetwork() {
// This test requires shims supporting T+ APIs (NsdServiceInfo.network)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -562,7 +611,7 @@
@Test
fun testNsdManager_RegisterOnNetwork() {
// This test requires shims supporting T+ APIs (NsdServiceInfo.network)
- assumeTrue(ConstantsShim.VERSION > SC_V2)
+ assumeTrue(TestUtils.shouldTestTApis())
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
@@ -608,12 +657,50 @@
}
}
+ @Test
+ fun testNsdManager_RegisterServiceNameWithNonStandardCharacters() {
+ val serviceNames = "^Nsd.Test|Non-#AsCiI\\Characters&\\ufffe テスト 測試"
+ val si = NsdServiceInfo().apply {
+ serviceType = SERVICE_TYPE
+ serviceName = serviceNames
+ port = 12345 // Test won't try to connect so port does not matter
+ }
+
+ // Register the service name which contains non-standard characters.
+ val registrationRecord = NsdRegistrationRecord()
+ nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, registrationRecord)
+ registrationRecord.expectCallback<ServiceRegistered>()
+
+ tryTest {
+ // Discover that service name.
+ val discoveryRecord = NsdDiscoveryRecord()
+ nsdManager.discoverServices(
+ SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord
+ )
+ val foundInfo = discoveryRecord.waitForServiceDiscovered(serviceNames)
+
+ // Expect that resolving the service name works properly even service name contains
+ // non-standard characters.
+ val resolveRecord = NsdResolveRecord()
+ nsdManager.resolveService(foundInfo, resolveRecord)
+ val resolvedCb = resolveRecord.expectCallback<ServiceResolved>()
+ assertEquals(foundInfo.serviceName, resolvedCb.serviceInfo.serviceName)
+ } cleanupStep {
+ nsdManager.unregisterService(registrationRecord)
+ } cleanup {
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+ }
+
/**
* Register a service and return its registration record.
*/
- private fun registerService(record: NsdRegistrationRecord, si: NsdServiceInfo): NsdServiceInfo {
- nsdShim.registerService(nsdManager, si, NsdManager.PROTOCOL_DNS_SD, Executor { it.run() },
- record)
+ private fun registerService(
+ record: NsdRegistrationRecord,
+ si: NsdServiceInfo,
+ executor: Executor = Executor { it.run() }
+ ): NsdServiceInfo {
+ nsdShim.registerService(nsdManager, si, NsdManager.PROTOCOL_DNS_SD, executor, record)
// We may not always get the name that we tried to register;
// This events tells us the name that was registered.
val cb = record.expectCallback<ServiceRegistered>()
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index e9c4e5a..6096a8b 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -56,7 +56,7 @@
defaults: ["CtsTetheringTestDefaults"],
min_sdk_version: "30",
- target_sdk_version: "30",
+ target_sdk_version: "33",
static_libs: [
"TetheringIntegrationTestsLatestSdkLib",
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index bd1b74a..6dfadc7 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -26,8 +26,13 @@
import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
import static android.net.cts.util.CtsTetheringUtils.isAnyIfaceMatch;
+import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
+import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -68,14 +73,19 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.TestCarrierConfigReceiver;
+
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -84,6 +94,9 @@
@RunWith(AndroidJUnit4.class)
public class TetheringManagerTest {
+ @Rule
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
private Context mContext;
private ConnectivityManager mCm;
@@ -392,7 +405,7 @@
// Override carrier config to ignore entitlement check.
final PersistableBundle bundle = new PersistableBundle();
bundle.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, false);
- overrideCarrierConfig(bundle);
+ overrideCarrierConfig(bundle, CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
// Verify that requestLatestTetheringEntitlementResult() can get entitlement
// result TETHER_ERROR_NO_ERROR due to provisioning bypassed.
@@ -400,14 +413,112 @@
TETHERING_WIFI, false, c -> c.run(), listener), TETHER_ERROR_NO_ERROR);
// Reset carrier config.
- overrideCarrierConfig(null);
+ overrideCarrierConfig(null, "");
}
- private void overrideCarrierConfig(PersistableBundle bundle) {
- final CarrierConfigManager configManager = (CarrierConfigManager) mContext
- .getSystemService(Context.CARRIER_CONFIG_SERVICE);
- final int subId = SubscriptionManager.getDefaultSubscriptionId();
- configManager.overrideConfig(subId, bundle);
+ @Test
+ @IgnoreUpTo(SC_V2)
+ public void testEnableTethering_carrierUnsupported_noTetheringActive() throws Exception {
+ assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
+
+ final TestTetheringEventCallback tetherEventCallback =
+ mCtsTetheringUtils.registerTetheringEventCallback();
+ boolean previousWifiEnabledState = false;
+ try {
+ tetherEventCallback.assumeWifiTetheringSupported(mContext);
+ // Avoid device connected to Wifi network.
+ previousWifiEnabledState = ensureCurrentNetworkIsCellular();
+ final PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, false);
+ // Override carrier config to make carrier not support.
+ overrideCarrierConfig(bundle, KEY_CARRIER_SUPPORTS_TETHERING_BOOL);
+
+ mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
+
+ mCtsTetheringUtils.expectSoftApDisabled();
+ tetherEventCallback.expectNoTetheringActive();
+ } finally {
+ overrideCarrierConfig(null, "");
+ mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+ if (previousWifiEnabledState) {
+ mCtsNetUtils.connectToWifi();
+ }
+ }
+ }
+
+ @Test
+ @IgnoreUpTo(SC_V2)
+ public void testEnableTethering_carrierUnsupportByConfigChange_noTetheringActive()
+ throws Exception {
+ assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
+
+ final TestTetheringEventCallback tetherEventCallback =
+ mCtsTetheringUtils.registerTetheringEventCallback();
+ boolean previousWifiEnabledState = false;
+ try {
+ tetherEventCallback.assumeWifiTetheringSupported(mContext);
+ // Avoid device connected to Wifi network.
+ previousWifiEnabledState = ensureCurrentNetworkIsCellular();
+ mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
+
+ final PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, false);
+ // Override carrier config to make carrier not support.
+ overrideCarrierConfig(bundle, KEY_CARRIER_SUPPORTS_TETHERING_BOOL);
+
+ mCtsTetheringUtils.expectSoftApDisabled();
+ tetherEventCallback.expectNoTetheringActive();
+ } finally {
+ overrideCarrierConfig(null, "");
+ mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+ if (previousWifiEnabledState) {
+ mCtsNetUtils.connectToWifi();
+ }
+ }
+ }
+
+ @Test
+ @IgnoreUpTo(SC_V2)
+ public void testRequestLatestEntitlementResult_carrierUnsupported() throws Exception {
+ assumeTrue(mTM.isTetheringSupported());
+ assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
+
+ final PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, false);
+ try {
+ // Override carrier config to make carrier not support.
+ overrideCarrierConfig(bundle, KEY_CARRIER_SUPPORTS_TETHERING_BOOL);
+
+ // Verify that requestLatestTetheringEntitlementResult() can get entitlement
+ // result TETHER_ERROR_PROVISIONING_FAILED due to carrier unsupported
+ assertEntitlementResult(listener -> mTM.requestLatestTetheringEntitlementResult(
+ TETHERING_WIFI,
+ false,
+ c -> c.run(),
+ listener),
+ TETHER_ERROR_PROVISIONING_FAILED);
+ } finally {
+ // Reset carrier config.
+ overrideCarrierConfig(null, "");
+ }
+ }
+
+ private void overrideCarrierConfig(PersistableBundle bundle, String configName)
+ throws Exception {
+ final int timeoutMs = 5_000;
+ final int currentSubId = SubscriptionManager.getDefaultSubscriptionId();
+ TestCarrierConfigReceiver configListener =
+ new TestCarrierConfigReceiver(mContext, currentSubId, timeoutMs, bundle,
+ (configs) -> {
+ if (bundle == null) {
+ // This is to restore carrier config and means no need to do match.
+ return true;
+ }
+ boolean requestConfigValue = bundle.getBoolean(configName);
+ boolean receiveConfigValue = configs.getBoolean(configName);
+ return Objects.equals(receiveConfigValue, requestConfigValue);
+ });
+ configListener.overrideCarrierConfigForTest();
}
@Test
@@ -421,30 +532,8 @@
try {
tetherEventCallback.assumeWifiTetheringSupported(mContext);
tetherEventCallback.expectNoTetheringActive();
-
- previousWifiEnabledState = mWm.isWifiEnabled();
- if (previousWifiEnabledState) {
- mCtsNetUtils.ensureWifiDisconnected(null);
- }
-
- final TestNetworkCallback networkCallback = new TestNetworkCallback();
- Network activeNetwork = null;
- try {
- mCm.registerDefaultNetworkCallback(networkCallback);
- activeNetwork = networkCallback.waitForAvailable();
- } finally {
- mCm.unregisterNetworkCallback(networkCallback);
- }
-
- assertNotNull("No active network. Please ensure the device has working mobile data.",
- activeNetwork);
- final NetworkCapabilities activeNetCap = mCm.getNetworkCapabilities(activeNetwork);
-
- // If active nework is ETHERNET, tethering may not use cell network as upstream.
- assumeFalse(activeNetCap.hasTransport(TRANSPORT_ETHERNET));
-
- assertTrue(activeNetCap.hasTransport(TRANSPORT_CELLULAR));
-
+ // Avoid device connected to Wifi network.
+ previousWifiEnabledState = ensureCurrentNetworkIsCellular();
mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
@@ -464,4 +553,36 @@
}
}
}
+
+ /**
+ * Make sure current network is cellular data.
+ * @return true Previous Wifi state is enabled, false is disabled.
+ */
+ private boolean ensureCurrentNetworkIsCellular() throws Exception {
+ final boolean previousWifiEnabledState = mWm.isWifiEnabled();
+ if (previousWifiEnabledState) {
+ mCtsNetUtils.ensureWifiDisconnected(null);
+ }
+
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+ Network activeNetwork = null;
+ try {
+ mCm.registerDefaultNetworkCallback(networkCallback);
+ activeNetwork = networkCallback.waitForAvailable();
+ } finally {
+ mCm.unregisterNetworkCallback(networkCallback);
+ }
+
+ assertNotNull("No active network. Please ensure the device has working mobile data.",
+ activeNetwork);
+
+ final NetworkCapabilities activeNetCap = mCm.getNetworkCapabilities(activeNetwork);
+
+ // If active nework is ETHERNET, tethering may not use cell network as upstream.
+ assumeFalse(activeNetCap.hasTransport(TRANSPORT_ETHERNET));
+
+ assertTrue(activeNetCap.hasTransport(TRANSPORT_CELLULAR));
+
+ return previousWifiEnabledState;
+ }
}
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index e3d80a0..97c1265 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -21,7 +21,7 @@
android_test {
name: "FrameworksNetIntegrationTests",
- defaults: ["framework-connectivity-internal-test-defaults"],
+ defaults: ["framework-connectivity-test-defaults"],
platform_apis: true,
certificate: "platform",
srcs: [
@@ -71,12 +71,8 @@
"net-tests-utils",
],
libs: [
- "service-connectivity-pre-jarjar",
+ "service-connectivity",
"services.core",
"services.net",
],
- visibility: [
- "//packages/modules/Connectivity/tests/integration",
- "//packages/modules/Connectivity/tests/unit",
- ],
}
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index 25694d7..db39e6f 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -42,7 +42,9 @@
#define PLATFORM "/sys/fs/bpf/"
#define TETHERING "/sys/fs/bpf/tethering/"
+#define PRIVATE "/sys/fs/bpf/net_private/"
#define SHARED "/sys/fs/bpf/net_shared/"
+#define NETD "/sys/fs/bpf/netd_shared/"
class BpfExistenceTest : public ::testing::Test {
};
@@ -95,32 +97,35 @@
SHARED "map_dscp_policy_ipv6_socket_to_policies_map_A",
SHARED "map_dscp_policy_ipv6_socket_to_policies_map_B",
SHARED "map_dscp_policy_switch_comp_map",
- SHARED "map_netd_app_uid_stats_map",
- SHARED "map_netd_configuration_map",
- SHARED "map_netd_cookie_tag_map",
- SHARED "map_netd_iface_index_name_map",
- SHARED "map_netd_iface_stats_map",
- SHARED "map_netd_stats_map_A",
- SHARED "map_netd_stats_map_B",
- SHARED "map_netd_uid_counterset_map",
- SHARED "map_netd_uid_owner_map",
- SHARED "map_netd_uid_permission_map",
- SHARED "prog_block_bind4_block_port",
- SHARED "prog_block_bind6_block_port",
+ NETD "map_netd_app_uid_stats_map",
+ NETD "map_netd_configuration_map",
+ NETD "map_netd_cookie_tag_map",
+ NETD "map_netd_iface_index_name_map",
+ NETD "map_netd_iface_stats_map",
+ NETD "map_netd_stats_map_A",
+ NETD "map_netd_stats_map_B",
+ NETD "map_netd_uid_counterset_map",
+ NETD "map_netd_uid_owner_map",
+ NETD "map_netd_uid_permission_map",
SHARED "prog_clatd_schedcls_egress4_clat_ether",
SHARED "prog_clatd_schedcls_egress4_clat_rawip",
SHARED "prog_clatd_schedcls_ingress6_clat_ether",
SHARED "prog_clatd_schedcls_ingress6_clat_rawip",
+ NETD "prog_netd_cgroupskb_egress_stats",
+ NETD "prog_netd_cgroupskb_ingress_stats",
+ NETD "prog_netd_cgroupsock_inet_create",
+ NETD "prog_netd_schedact_ingress_account",
+ NETD "prog_netd_skfilter_allowlist_xtbpf",
+ NETD "prog_netd_skfilter_denylist_xtbpf",
+ NETD "prog_netd_skfilter_egress_xtbpf",
+ NETD "prog_netd_skfilter_ingress_xtbpf",
+};
+
+static const set<string> INTRODUCED_T_5_4 = {
+ SHARED "prog_block_bind4_block_port",
+ SHARED "prog_block_bind6_block_port",
SHARED "prog_dscp_policy_schedcls_set_dscp_ether",
SHARED "prog_dscp_policy_schedcls_set_dscp_raw_ip",
- SHARED "prog_netd_cgroupskb_egress_stats",
- SHARED "prog_netd_cgroupskb_ingress_stats",
- SHARED "prog_netd_cgroupsock_inet_create",
- SHARED "prog_netd_schedact_ingress_account",
- SHARED "prog_netd_skfilter_allowlist_xtbpf",
- SHARED "prog_netd_skfilter_denylist_xtbpf",
- SHARED "prog_netd_skfilter_egress_xtbpf",
- SHARED "prog_netd_skfilter_ingress_xtbpf",
};
static const set<string> REMOVED_T = {
@@ -162,6 +167,7 @@
if (IsAtLeastT()) {
addAll(expected, INTRODUCED_T);
+ if (android::bpf::isAtLeastKernelVersion(5, 4, 0)) addAll(expected, INTRODUCED_T_5_4);
removeAll(expected, REMOVED_T);
addAll(unexpected, REMOVED_T);
diff --git a/tests/native/Android.bp b/tests/native/Android.bp
index a8d908a..7d43aa8 100644
--- a/tests/native/Android.bp
+++ b/tests/native/Android.bp
@@ -31,3 +31,10 @@
],
compile_multilib: "first",
}
+
+filegroup {
+ name: "net_native_test_config_template",
+ srcs: [
+ "NetNativeTestConfigTemplate.xml",
+ ],
+}
diff --git a/tests/native/NetNativeTestConfigTemplate.xml b/tests/native/NetNativeTestConfigTemplate.xml
new file mode 100644
index 0000000..b71e9aa
--- /dev/null
+++ b/tests/native/NetNativeTestConfigTemplate.xml
@@ -0,0 +1,31 @@
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Configuration for {MODULE} tests">
+ <option name="test-suite-tag" value="mts" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
+ <!-- Only run tests if the device under test is SDK version 33 (Android 13) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="{MODULE}" />
+ </test>
+</configuration>
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 5a7208c..3ea27f7 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -54,42 +54,21 @@
filegroup {
name: "non-connectivity-module-test",
srcs: [
- "java/android/app/usage/*.java",
- "java/android/net/EthernetNetworkUpdateRequestTest.java",
"java/android/net/Ikev2VpnProfileTest.java",
"java/android/net/IpMemoryStoreTest.java",
- "java/android/net/IpSecAlgorithmTest.java",
- "java/android/net/IpSecConfigTest.java",
- "java/android/net/IpSecManagerTest.java",
- "java/android/net/IpSecTransformTest.java",
- "java/android/net/KeepalivePacketDataUtilTest.java",
- "java/android/net/NetworkIdentitySetTest.kt",
- "java/android/net/NetworkIdentityTest.kt",
- "java/android/net/NetworkStats*.java",
- "java/android/net/NetworkTemplateTest.kt",
"java/android/net/TelephonyNetworkSpecifierTest.java",
"java/android/net/VpnManagerTest.java",
"java/android/net/ipmemorystore/*.java",
"java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt",
- "java/android/net/nsd/*.java",
"java/com/android/internal/net/NetworkUtilsInternalTest.java",
"java/com/android/internal/net/VpnProfileTest.java",
- "java/com/android/server/IpSecServiceParameterizedTest.java",
- "java/com/android/server/IpSecServiceRefcountedResourceTest.java",
- "java/com/android/server/IpSecServiceTest.java",
"java/com/android/server/NetworkManagementServiceTest.java",
- "java/com/android/server/NsdServiceTest.java",
"java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java",
"java/com/android/server/connectivity/IpConnectivityMetricsTest.java",
"java/com/android/server/connectivity/MultipathPolicyTrackerTest.java",
"java/com/android/server/connectivity/NetdEventListenerServiceTest.java",
"java/com/android/server/connectivity/VpnTest.java",
- "java/com/android/server/ethernet/*.java",
"java/com/android/server/net/ipmemorystore/*.java",
- "java/com/android/server/net/BpfInterfaceMapUpdaterTest.java",
- "java/com/android/server/net/IpConfigStoreTest.java",
- "java/com/android/server/net/NetworkStats*.java",
- "java/com/android/server/net/TestableUsageCallback.kt",
]
}
diff --git a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
index 561e621..b1b76ec 100644
--- a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
+++ b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
@@ -54,7 +54,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NetworkStatsManagerTest {
private static final String TEST_SUBSCRIBER_ID = "subid";
diff --git a/tests/unit/java/android/net/IpSecAlgorithmTest.java b/tests/unit/java/android/net/IpSecAlgorithmTest.java
index c473e82..1482055 100644
--- a/tests/unit/java/android/net/IpSecAlgorithmTest.java
+++ b/tests/unit/java/android/net/IpSecAlgorithmTest.java
@@ -47,7 +47,7 @@
/** Unit tests for {@link IpSecAlgorithm}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecAlgorithmTest {
private static final byte[] KEY_MATERIAL;
diff --git a/tests/unit/java/android/net/IpSecConfigTest.java b/tests/unit/java/android/net/IpSecConfigTest.java
index b87cb48..9f83036 100644
--- a/tests/unit/java/android/net/IpSecConfigTest.java
+++ b/tests/unit/java/android/net/IpSecConfigTest.java
@@ -36,7 +36,7 @@
/** Unit tests for {@link IpSecConfig}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecConfigTest {
@Test
diff --git a/tests/unit/java/android/net/IpSecManagerTest.java b/tests/unit/java/android/net/IpSecManagerTest.java
index cda8eb7..335f539 100644
--- a/tests/unit/java/android/net/IpSecManagerTest.java
+++ b/tests/unit/java/android/net/IpSecManagerTest.java
@@ -52,7 +52,7 @@
/** Unit tests for {@link IpSecManager}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecManagerTest {
private static final int TEST_UDP_ENCAP_PORT = 34567;
diff --git a/tests/unit/java/android/net/IpSecTransformTest.java b/tests/unit/java/android/net/IpSecTransformTest.java
index 81375f1..c1bd719 100644
--- a/tests/unit/java/android/net/IpSecTransformTest.java
+++ b/tests/unit/java/android/net/IpSecTransformTest.java
@@ -32,7 +32,7 @@
/** Unit tests for {@link IpSecTransform}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecTransformTest {
@Test
diff --git a/tests/unit/java/android/net/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt
index bf5568d..d84328c 100644
--- a/tests/unit/java/android/net/NetworkIdentityTest.kt
+++ b/tests/unit/java/android/net/NetworkIdentityTest.kt
@@ -47,7 +47,7 @@
private const val TEST_SUBID2 = 2
@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class NetworkIdentityTest {
private val mockContext = mock(Context::class.java)
diff --git a/tests/unit/java/android/net/NetworkStatsAccessTest.java b/tests/unit/java/android/net/NetworkStatsAccessTest.java
index 97a93ca..a74056b 100644
--- a/tests/unit/java/android/net/NetworkStatsAccessTest.java
+++ b/tests/unit/java/android/net/NetworkStatsAccessTest.java
@@ -19,6 +19,7 @@
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.when;
import android.Manifest;
@@ -66,6 +67,10 @@
when(mContext.getSystemServiceName(DevicePolicyManager.class))
.thenReturn(Context.DEVICE_POLICY_SERVICE);
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(mDpm);
+ if (mContext.getSystemService(DevicePolicyManager.class) == null) {
+ // Test is using mockito-extended
+ doCallRealMethod().when(mContext).getSystemService(DevicePolicyManager.class);
+ }
setHasCarrierPrivileges(false);
setIsDeviceOwner(false);
diff --git a/tests/unit/java/android/net/NetworkStatsHistoryTest.java b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
index 26079a2..43e331b 100644
--- a/tests/unit/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
@@ -60,7 +60,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NetworkStatsHistoryTest {
private static final String TAG = "NetworkStatsHistoryTest";
diff --git a/tests/unit/java/android/net/NetworkStatsTest.java b/tests/unit/java/android/net/NetworkStatsTest.java
index b0cc16c..6d79869 100644
--- a/tests/unit/java/android/net/NetworkStatsTest.java
+++ b/tests/unit/java/android/net/NetworkStatsTest.java
@@ -61,7 +61,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NetworkStatsTest {
private static final String TEST_IFACE = "test0";
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index abd1825..3e9662d 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -70,7 +70,7 @@
private const val TEST_WIFI_KEY2 = "wifiKey2"
@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class NetworkTemplateTest {
private val mockContext = mock(Context::class.java)
private val mockWifiInfo = mock(WifiInfo::class.java)
diff --git a/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt b/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt
index 743d39e..aa5a246 100644
--- a/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt
+++ b/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt
@@ -61,14 +61,6 @@
assertValues(builder.build(), 55, 1814302L, 21050L, 31001636L, 26152L)
}
- @Test
- fun testMaybeReadLegacyUid() {
- val builder = NetworkStatsCollection.Builder(BUCKET_DURATION_MS)
- NetworkStatsDataMigrationUtils.readLegacyUid(builder,
- getInputStreamForResource(R.raw.netstats_uid_v4), false /* taggedData */)
- assertValues(builder.build(), 223, 106245210L, 710722L, 1130647496L, 1103989L)
- }
-
private fun assertValues(
collection: NetworkStatsCollection,
expectedSize: Int,
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index 30b8fcd..32274bc 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -51,7 +51,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NsdManagerTest {
static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
diff --git a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
index e5e7ebc..64355ed 100644
--- a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
+++ b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
@@ -42,7 +42,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NsdServiceInfoTest {
public final static InetAddress LOCALHOST;
@@ -125,6 +125,7 @@
fullInfo.setPort(4242);
fullInfo.setHost(LOCALHOST);
fullInfo.setNetwork(new Network(123));
+ fullInfo.setInterfaceIndex(456);
checkParcelable(fullInfo);
NsdServiceInfo noHostInfo = new NsdServiceInfo();
@@ -175,6 +176,7 @@
assertEquals(original.getHost(), result.getHost());
assertTrue(original.getPort() == result.getPort());
assertEquals(original.getNetwork(), result.getNetwork());
+ assertEquals(original.getInterfaceIndex(), result.getInterfaceIndex());
// Assert equality of attribute map.
Map<String, byte[]> originalMap = original.getAttributes();
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 52cebec..e84df16 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -51,8 +51,17 @@
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOCKDOWN_VPN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
@@ -9505,7 +9514,7 @@
b2.expectBroadcast();
}
- @Test
+ @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testLockdownSetFirewallUidRule() throws Exception {
// For ConnectivityService#setAlwaysOnVpnPackage.
mServiceContext.setPermission(
@@ -9545,6 +9554,98 @@
verify(mBpfNetMaps, never()).removeUidInterfaceRules(any());
}
+ private void doTestSetUidFirewallRule(final int chain, final int defaultRule) {
+ final int uid = 1001;
+ mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_ALLOW);
+ verify(mBpfNetMaps).setUidRule(chain, uid, FIREWALL_RULE_ALLOW);
+ reset(mBpfNetMaps);
+
+ mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_DENY);
+ verify(mBpfNetMaps).setUidRule(chain, uid, FIREWALL_RULE_DENY);
+ reset(mBpfNetMaps);
+
+ mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_DEFAULT);
+ verify(mBpfNetMaps).setUidRule(chain, uid, defaultRule);
+ reset(mBpfNetMaps);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testSetUidFirewallRule() throws Exception {
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_DOZABLE, FIREWALL_RULE_DENY);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_STANDBY, FIREWALL_RULE_ALLOW);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_POWERSAVE, FIREWALL_RULE_DENY);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, FIREWALL_RULE_DENY);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, FIREWALL_RULE_DENY);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_1, FIREWALL_RULE_ALLOW);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_2, FIREWALL_RULE_ALLOW);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_3, FIREWALL_RULE_ALLOW);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testSetFirewallChainEnabled() throws Exception {
+ final List<Integer> firewallChains = Arrays.asList(
+ FIREWALL_CHAIN_DOZABLE,
+ FIREWALL_CHAIN_STANDBY,
+ FIREWALL_CHAIN_POWERSAVE,
+ FIREWALL_CHAIN_RESTRICTED,
+ FIREWALL_CHAIN_LOW_POWER_STANDBY,
+ FIREWALL_CHAIN_OEM_DENY_1,
+ FIREWALL_CHAIN_OEM_DENY_2,
+ FIREWALL_CHAIN_OEM_DENY_3);
+ for (final int chain: firewallChains) {
+ mCm.setFirewallChainEnabled(chain, true /* enabled */);
+ verify(mBpfNetMaps).setChildChain(chain, true /* enable */);
+ reset(mBpfNetMaps);
+
+ mCm.setFirewallChainEnabled(chain, false /* enabled */);
+ verify(mBpfNetMaps).setChildChain(chain, false /* enable */);
+ reset(mBpfNetMaps);
+ }
+ }
+
+ private void doTestReplaceFirewallChain(final int chain, final String chainName,
+ final boolean allowList) {
+ final int[] uids = new int[] {1001, 1002};
+ mCm.replaceFirewallChain(chain, uids);
+ verify(mBpfNetMaps).replaceUidChain(chainName, allowList, uids);
+ reset(mBpfNetMaps);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testReplaceFirewallChain() {
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_DOZABLE, "fw_dozable", true);
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_STANDBY, "fw_standby", false);
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_POWERSAVE, "fw_powersave", true);
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_RESTRICTED, "fw_restricted", true);
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_LOW_POWER_STANDBY, "fw_low_power_standby", true);
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_1, "fw_oem_deny_1", false);
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_2, "fw_oem_deny_2", false);
+ doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_3, "fw_oem_deny_3", false);
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testInvalidFirewallChain() throws Exception {
+ final int uid = 1001;
+ final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+ assertThrows(expected,
+ () -> mCm.setUidFirewallRule(-1 /* chain */, uid, FIREWALL_RULE_ALLOW));
+ assertThrows(expected,
+ () -> mCm.setUidFirewallRule(100 /* chain */, uid, FIREWALL_RULE_ALLOW));
+ assertThrows(expected, () -> mCm.replaceFirewallChain(-1 /* chain */, new int[]{uid}));
+ assertThrows(expected, () -> mCm.replaceFirewallChain(100 /* chain */, new int[]{uid}));
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testInvalidFirewallRule() throws Exception {
+ final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+ assertThrows(expected,
+ () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_DOZABLE,
+ 1001 /* uid */, -1 /* rule */));
+ assertThrows(expected,
+ () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_DOZABLE,
+ 1001 /* uid */, 100 /* rule */));
+ }
+
/**
* Test mutable and requestable network capabilities such as
* {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} and
@@ -10428,7 +10529,7 @@
}
@Test
- public void testLegacyVpnSetInterfaceFilteringRuleWithWildcard() throws Exception {
+ public void testLegacyVpnInterfaceFilteringRule() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("tun0");
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
@@ -10438,29 +10539,34 @@
mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
- // A connected Legacy VPN should have interface rules with null interface.
- // Null Interface is a wildcard and this accepts traffic from all the interfaces.
- // There are two expected invocations, one during the VPN initial connection,
- // one during the VPN LinkProperties update.
- ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
- verify(mBpfNetMaps, times(2)).addUidInterfaceRules(
- eq(null) /* iface */, uidCaptor.capture());
- assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID, VPN_UID);
- assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID, VPN_UID);
- assertEquals(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */),
- vpnRange);
+ if (SdkLevel.isAtLeastT()) {
+ // On T and above, A connected Legacy VPN should have interface rules with null
+ // interface. Null Interface is a wildcard and this accepts traffic from all the
+ // interfaces. There are two expected invocations, one during the VPN initial
+ // connection, one during the VPN LinkProperties update.
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mBpfNetMaps, times(2)).addUidInterfaceRules(
+ eq(null) /* iface */, uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID, VPN_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID, VPN_UID);
+ assertEquals(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */),
+ vpnRange);
- mMockVpn.disconnect();
- waitForIdle();
+ mMockVpn.disconnect();
+ waitForIdle();
- // Disconnected VPN should have interface rules removed
- verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
- assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID, VPN_UID);
- assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */));
+ // Disconnected VPN should have interface rules removed
+ verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID, VPN_UID);
+ assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */));
+ } else {
+ // Before T, Legacy VPN should not have interface rules.
+ verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
+ }
}
@Test
- public void testLocalIpv4OnlyVpnSetInterfaceFilteringRuleWithWildcard() throws Exception {
+ public void testLocalIpv4OnlyVpnInterfaceFilteringRule() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("tun0");
lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun0"));
@@ -10470,26 +10576,31 @@
mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
- // IPv6 unreachable route should not be misinterpreted as a default route
- // A connected VPN should have interface rules with null interface.
- // Null Interface is a wildcard and this accepts traffic from all the interfaces.
- // There are two expected invocations, one during the VPN initial connection,
- // one during the VPN LinkProperties update.
- ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
- verify(mBpfNetMaps, times(2)).addUidInterfaceRules(
- eq(null) /* iface */, uidCaptor.capture());
- assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID, VPN_UID);
- assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID, VPN_UID);
- assertEquals(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */),
- vpnRange);
+ if (SdkLevel.isAtLeastT()) {
+ // IPv6 unreachable route should not be misinterpreted as a default route
+ // On T and above, A connected VPN that does not provide a default route should have
+ // interface rules with null interface. Null Interface is a wildcard and this accepts
+ // traffic from all the interfaces. There are two expected invocations, one during the
+ // VPN initial connection, one during the VPN LinkProperties update.
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mBpfNetMaps, times(2)).addUidInterfaceRules(
+ eq(null) /* iface */, uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID, VPN_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID, VPN_UID);
+ assertEquals(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */),
+ vpnRange);
- mMockVpn.disconnect();
- waitForIdle();
+ mMockVpn.disconnect();
+ waitForIdle();
- // Disconnected VPN should have interface rules removed
- verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
- assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID, VPN_UID);
- assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */));
+ // Disconnected VPN should have interface rules removed
+ verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID, VPN_UID);
+ assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */));
+ } else {
+ // Before T, VPN with IPv6 unreachable route should not have interface rules.
+ verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
+ }
}
@Test
@@ -11694,6 +11805,12 @@
mCm.unregisterNetworkCallback(networkCallback);
}
+ private void verifyDump(String[] args) {
+ final StringWriter stringWriter = new StringWriter();
+ mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), args);
+ assertFalse(stringWriter.toString().isEmpty());
+ }
+
@Test
public void testDumpDoesNotCrash() {
mServiceContext.setPermission(DUMP, PERMISSION_GRANTED);
@@ -11706,11 +11823,26 @@
.addTransportType(TRANSPORT_WIFI).build();
mCm.registerNetworkCallback(genericRequest, genericNetworkCallback);
mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
- final StringWriter stringWriter = new StringWriter();
- mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), new String[0]);
+ verifyDump(new String[0]);
- assertFalse(stringWriter.toString().isEmpty());
+ // Verify dump with arguments.
+ final String dumpPrio = "--dump-priority";
+ final String[] dumpArgs = {dumpPrio};
+ verifyDump(dumpArgs);
+
+ final String[] highDumpArgs = {dumpPrio, "HIGH"};
+ verifyDump(highDumpArgs);
+
+ final String[] normalDumpArgs = {dumpPrio, "NORMAL"};
+ verifyDump(normalDumpArgs);
+
+ // Invalid args should do dumpNormal w/o exception
+ final String[] unknownDumpArgs = {dumpPrio, "UNKNOWN"};
+ verifyDump(unknownDumpArgs);
+
+ final String[] invalidDumpArgs = {"UNKNOWN"};
+ verifyDump(invalidDumpArgs);
}
@Test
@@ -15519,6 +15651,27 @@
}
@Test
+ public void testAutomotiveEthernetAllowedUids() throws Exception {
+ mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+ mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+
+ // In this test the automotive feature will be enabled.
+ mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);
+
+ // Simulate a restricted ethernet network.
+ final NetworkCapabilities.Builder agentNetCaps = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_ETHERNET)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+
+ mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET,
+ new LinkProperties(), agentNetCaps.build());
+ validateAllowedUids(mEthernetNetworkAgent, TRANSPORT_ETHERNET, agentNetCaps, true);
+ }
+
+ @Test
public void testCbsAllowedUids() throws Exception {
mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
@@ -15527,6 +15680,24 @@
doReturn(true).when(mCarrierPrivilegeAuthenticator)
.hasCarrierPrivilegeForNetworkCapabilities(eq(TEST_PACKAGE_UID), any());
+ // Simulate a restricted telephony network. The telephony factory is entitled to set
+ // the access UID to the service package on any of its restricted networks.
+ final NetworkCapabilities.Builder agentNetCaps = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
+
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
+ new LinkProperties(), agentNetCaps.build());
+ validateAllowedUids(mCellNetworkAgent, TRANSPORT_CELLULAR, agentNetCaps, false);
+ }
+
+ private void validateAllowedUids(final TestNetworkAgentWrapper testAgent,
+ @NetworkCapabilities.Transport final int transportUnderTest,
+ final NetworkCapabilities.Builder ncb, final boolean forAutomotive) throws Exception {
final ArraySet<Integer> serviceUidSet = new ArraySet<>();
serviceUidSet.add(TEST_PACKAGE_UID);
final ArraySet<Integer> nonServiceUidSet = new ArraySet<>();
@@ -15537,40 +15708,34 @@
final TestNetworkCallback cb = new TestNetworkCallback();
- // Simulate a restricted telephony network. The telephony factory is entitled to set
- // the access UID to the service package on any of its restricted networks.
- final NetworkCapabilities.Builder ncb = new NetworkCapabilities.Builder()
- .addTransportType(TRANSPORT_CELLULAR)
- .addCapability(NET_CAPABILITY_INTERNET)
- .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
- .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
- .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
- .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
-
+ /* Test setting UIDs */
// Cell gets to set the service UID as access UID
mCm.requestNetwork(new NetworkRequest.Builder()
- .addTransportType(TRANSPORT_CELLULAR)
+ .addTransportType(transportUnderTest)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
.build(), cb);
- mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
- new LinkProperties(), ncb.build());
- mCellNetworkAgent.connect(true);
- cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ testAgent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(testAgent);
ncb.setAllowedUids(serviceUidSet);
- mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
if (SdkLevel.isAtLeastT()) {
- cb.expectCapabilitiesThat(mCellNetworkAgent,
+ cb.expectCapabilitiesThat(testAgent,
caps -> caps.getAllowedUids().equals(serviceUidSet));
} else {
// S must ignore access UIDs.
cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
}
+ /* Test setting UIDs is rejected when expected */
+ if (forAutomotive) {
+ mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false);
+ }
+
// ...but not to some other UID. Rejection sets UIDs to the empty set
ncb.setAllowedUids(nonServiceUidSet);
- mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
if (SdkLevel.isAtLeastT()) {
- cb.expectCapabilitiesThat(mCellNetworkAgent,
+ cb.expectCapabilitiesThat(testAgent,
caps -> caps.getAllowedUids().isEmpty());
} else {
// S must ignore access UIDs.
@@ -15579,18 +15744,18 @@
// ...and also not to multiple UIDs even including the service UID
ncb.setAllowedUids(serviceUidSetPlus);
- mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
- mCellNetworkAgent.disconnect();
- cb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ testAgent.disconnect();
+ cb.expectCallback(CallbackEntry.LOST, testAgent);
mCm.unregisterNetworkCallback(cb);
// Must be unset before touching the transports, because remove and add transport types
// check the specifier on the builder immediately, contradicting normal builder semantics
// TODO : fix the builder
ncb.setNetworkSpecifier(null);
- ncb.removeTransportType(TRANSPORT_CELLULAR);
+ ncb.removeTransportType(transportUnderTest);
ncb.addTransportType(TRANSPORT_WIFI);
// Wifi does not get to set access UID, even to the correct UID
mCm.requestNetwork(new NetworkRequest.Builder()
diff --git a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
index 45f3d3c..9401d47 100644
--- a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -89,7 +89,7 @@
public class IpSecServiceParameterizedTest {
@Rule
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(
- Build.VERSION_CODES.R /* ignoreClassUpTo */);
+ Build.VERSION_CODES.S_V2 /* ignoreClassUpTo */);
private static final int TEST_SPI = 0xD1201D;
@@ -783,6 +783,23 @@
}
@Test
+ public void testSetNetworkForTunnelInterfaceFailsForNullLp() throws Exception {
+ final IpSecTunnelInterfaceResponse createTunnelResp =
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+ final Network newFakeNetwork = new Network(1000);
+ final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+
+ try {
+ mIpSecService.setNetworkForTunnelInterface(
+ tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+ fail(
+ "Expected an IllegalArgumentException for underlying network with null"
+ + " LinkProperties");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception {
final IpSecTunnelInterfaceResponse createTunnelResp =
createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
diff --git a/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
index 5c7ca6f..8595ab9 100644
--- a/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
@@ -54,7 +54,7 @@
/** Unit tests for {@link IpSecService.RefcountedResource}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecServiceRefcountedResourceTest {
Context mMockContext;
IpSecService.Dependencies mMockDeps;
diff --git a/tests/unit/java/com/android/server/IpSecServiceTest.java b/tests/unit/java/com/android/server/IpSecServiceTest.java
index 7e6b157..6955620 100644
--- a/tests/unit/java/com/android/server/IpSecServiceTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceTest.java
@@ -75,7 +75,7 @@
/** Unit tests for {@link IpSecService}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecServiceTest {
private static final int DROID_SPI = 0xD1201D;
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 3c228d0..9365bee 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -19,10 +19,13 @@
import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@@ -36,6 +39,12 @@
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ContentResolver;
import android.content.Context;
+import android.net.INetd;
+import android.net.InetAddresses;
+import android.net.mdns.aidl.DiscoveryInfo;
+import android.net.mdns.aidl.GetAddressInfo;
+import android.net.mdns.aidl.IMDnsEventListener;
+import android.net.mdns.aidl.ResolutionInfo;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.INsdServiceConnector;
import android.net.nsd.MDnsManager;
@@ -63,6 +72,7 @@
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.AdditionalAnswers;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -74,7 +84,7 @@
// - test NSD_ON ENABLE/DISABLED listening
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NsdServiceTest {
static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
@@ -114,6 +124,10 @@
doReturn(MDnsManager.MDNS_SERVICE).when(mContext)
.getSystemServiceName(MDnsManager.class);
doReturn(mMockMDnsM).when(mContext).getSystemService(MDnsManager.MDNS_SERVICE);
+ if (mContext.getSystemService(MDnsManager.class) == null) {
+ // Test is using mockito-extended
+ doCallRealMethod().when(mContext).getSystemService(MDnsManager.class);
+ }
doReturn(true).when(mMockMDnsM).registerService(
anyInt(), anyString(), anyString(), anyInt(), any(), anyInt());
doReturn(true).when(mMockMDnsM).stopOperation(anyInt());
@@ -275,6 +289,105 @@
verify(mMockMDnsM, never()).stopDaemon();
}
+ @Test
+ public void testDiscoverOnTetheringDownstream() throws Exception {
+ NsdService service = makeService();
+ NsdManager client = connectClient(service);
+
+ final String serviceType = "a_type";
+ final String serviceName = "a_name";
+ final String domainName = "mytestdevice.local";
+ final int interfaceIdx = 123;
+ final NsdManager.DiscoveryListener discListener = mock(NsdManager.DiscoveryListener.class);
+ client.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discListener);
+ waitForIdle();
+
+ final ArgumentCaptor<IMDnsEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(IMDnsEventListener.class);
+ verify(mMockMDnsM).registerEventListener(listenerCaptor.capture());
+ final ArgumentCaptor<Integer> discIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMockMDnsM).discover(discIdCaptor.capture(), eq(serviceType),
+ eq(0) /* interfaceIdx */);
+ // NsdManager uses a separate HandlerThread to dispatch callbacks (on ServiceHandler), so
+ // this needs to use a timeout
+ verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(serviceType);
+
+ final DiscoveryInfo discoveryInfo = new DiscoveryInfo(
+ discIdCaptor.getValue(),
+ IMDnsEventListener.SERVICE_FOUND,
+ serviceName,
+ serviceType,
+ domainName,
+ interfaceIdx,
+ INetd.LOCAL_NET_ID); // LOCAL_NET_ID (99) used on tethering downstreams
+ final IMDnsEventListener eventListener = listenerCaptor.getValue();
+ eventListener.onServiceDiscoveryStatus(discoveryInfo);
+ waitForIdle();
+
+ final ArgumentCaptor<NsdServiceInfo> discoveredInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ verify(discListener, timeout(TIMEOUT_MS)).onServiceFound(discoveredInfoCaptor.capture());
+ final NsdServiceInfo foundInfo = discoveredInfoCaptor.getValue();
+ assertEquals(serviceName, foundInfo.getServiceName());
+ assertEquals(serviceType, foundInfo.getServiceType());
+ assertNull(foundInfo.getHost());
+ assertNull(foundInfo.getNetwork());
+ assertEquals(interfaceIdx, foundInfo.getInterfaceIndex());
+
+ // After discovering the service, verify resolving it
+ final NsdManager.ResolveListener resolveListener = mock(NsdManager.ResolveListener.class);
+ client.resolveService(foundInfo, resolveListener);
+ waitForIdle();
+
+ final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(serviceName), eq(serviceType),
+ eq("local.") /* domain */, eq(interfaceIdx));
+
+ final int servicePort = 10123;
+ final String serviceFullName = serviceName + "." + serviceType;
+ final ResolutionInfo resolutionInfo = new ResolutionInfo(
+ resolvIdCaptor.getValue(),
+ IMDnsEventListener.SERVICE_RESOLVED,
+ null /* serviceName */,
+ null /* serviceType */,
+ null /* domain */,
+ serviceFullName,
+ domainName,
+ servicePort,
+ new byte[0] /* txtRecord */,
+ interfaceIdx);
+
+ doReturn(true).when(mMockMDnsM).getServiceAddress(anyInt(), any(), anyInt());
+ eventListener.onServiceResolutionStatus(resolutionInfo);
+ waitForIdle();
+
+ final ArgumentCaptor<Integer> getAddrIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMockMDnsM).getServiceAddress(getAddrIdCaptor.capture(), eq(domainName),
+ eq(interfaceIdx));
+
+ final String serviceAddress = "192.0.2.123";
+ final GetAddressInfo addressInfo = new GetAddressInfo(
+ getAddrIdCaptor.getValue(),
+ IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS,
+ serviceFullName,
+ serviceAddress,
+ interfaceIdx,
+ INetd.LOCAL_NET_ID);
+ eventListener.onGettingServiceAddressStatus(addressInfo);
+ waitForIdle();
+
+ final ArgumentCaptor<NsdServiceInfo> resInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(resInfoCaptor.capture());
+ final NsdServiceInfo resolvedService = resInfoCaptor.getValue();
+ assertEquals(serviceName, resolvedService.getServiceName());
+ assertEquals("." + serviceType, resolvedService.getServiceType());
+ assertEquals(InetAddresses.parseNumericAddress(serviceAddress), resolvedService.getHost());
+ assertEquals(servicePort, resolvedService.getPort());
+ assertNull(resolvedService.getNetwork());
+ assertEquals(interfaceIdx, resolvedService.getInterfaceIndex());
+ }
+
private void waitForIdle() {
HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
}
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index f84d10f..3047a16 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -30,12 +30,15 @@
import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
import android.net.INetd;
@@ -46,6 +49,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.bpf.ClatEgress4Key;
import com.android.net.module.util.bpf.ClatEgress4Value;
@@ -53,6 +57,7 @@
import com.android.net.module.util.bpf.ClatIngress6Value;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.TestBpfMap;
import org.junit.Before;
import org.junit.Test;
@@ -64,6 +69,7 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.StringWriter;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.util.Objects;
@@ -101,12 +107,12 @@
private static final int RAW_SOCK_FD = 535;
private static final int PACKET_SOCK_FD = 536;
private static final long RAW_SOCK_COOKIE = 27149;
- private static final ParcelFileDescriptor TUN_PFD = new ParcelFileDescriptor(
- new FileDescriptor());
- private static final ParcelFileDescriptor RAW_SOCK_PFD = new ParcelFileDescriptor(
- new FileDescriptor());
- private static final ParcelFileDescriptor PACKET_SOCK_PFD = new ParcelFileDescriptor(
- new FileDescriptor());
+ private static final ParcelFileDescriptor TUN_PFD = spy(new ParcelFileDescriptor(
+ new FileDescriptor()));
+ private static final ParcelFileDescriptor RAW_SOCK_PFD = spy(new ParcelFileDescriptor(
+ new FileDescriptor()));
+ private static final ParcelFileDescriptor PACKET_SOCK_PFD = spy(new ParcelFileDescriptor(
+ new FileDescriptor()));
private static final String EGRESS_PROG_PATH =
"/sys/fs/bpf/net_shared/prog_clatd_schedcls_egress4_clat_rawip";
@@ -121,10 +127,13 @@
private static final ClatIngress6Value INGRESS_VALUE = new ClatIngress6Value(STACKED_IFINDEX,
INET4_LOCAL4);
+ private final TestBpfMap<ClatIngress6Key, ClatIngress6Value> mIngressMap =
+ spy(new TestBpfMap<>(ClatIngress6Key.class, ClatIngress6Value.class));
+ private final TestBpfMap<ClatEgress4Key, ClatEgress4Value> mEgressMap =
+ spy(new TestBpfMap<>(ClatEgress4Key.class, ClatEgress4Value.class));
+
@Mock private INetd mNetd;
@Spy private TestDependencies mDeps = new TestDependencies();
- @Mock private IBpfMap<ClatIngress6Key, ClatIngress6Value> mIngressMap;
- @Mock private IBpfMap<ClatEgress4Key, ClatEgress4Value> mEgressMap;
/**
* The dependency injection class is used to mock the JNI functions and system functions
@@ -505,4 +514,190 @@
// Expected mtu is that CLAT_MAX_MTU(65536) minus MTU_DELTA(28).
assertEquals(65508, ClatCoordinator.adjustMtu(CLAT_MAX_MTU + 1 /* over maximum mtu */));
}
+
+ @Test
+ public void testDump() throws Exception {
+ final ClatCoordinator coordinator = makeClatCoordinator();
+ final StringWriter stringWriter = new StringWriter();
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(stringWriter, " ");
+ coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX);
+ coordinator.dump(ipw);
+
+ final String[] dumpStrings = stringWriter.toString().split("\n");
+ assertEquals(5, dumpStrings.length);
+ assertEquals("Forwarding rules:", dumpStrings[0].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",
+ dumpStrings[3].trim());
+ assertEquals("1001 /192.0.0.46 -> /2001:db8:0:b11::464 /64:ff9b::/96 1000 ether",
+ dumpStrings[4].trim());
+ }
+
+ @Test
+ public void testNotStartClatWithInvalidPrefix() throws Exception {
+ final ClatCoordinator coordinator = makeClatCoordinator();
+ final IpPrefix invalidPrefix = new IpPrefix("2001:db8::/64");
+ assertThrows(IOException.class,
+ () -> coordinator.clatStart(BASE_IFACE, NETID, invalidPrefix));
+ }
+
+ private void assertStartClat(final TestDependencies deps) throws Exception {
+ final ClatCoordinator coordinator = new ClatCoordinator(deps);
+ assertNotNull(coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
+ }
+
+ private void assertNotStartClat(final TestDependencies deps) {
+ // Expect that the injection function of TestDependencies causes clatStart() failed.
+ final ClatCoordinator coordinator = new ClatCoordinator(deps);
+ assertThrows(IOException.class,
+ () -> coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
+ }
+
+ private void checkNotStartClat(final TestDependencies deps, final boolean verifyTunFd,
+ final boolean verifyPacketSockFd, final boolean verifyRawSockFd) throws Exception {
+ // [1] Expect that modified TestDependencies can't start clatd.
+ clearInvocations(TUN_PFD, RAW_SOCK_PFD, PACKET_SOCK_PFD);
+ assertNotStartClat(deps);
+ if (verifyTunFd) verify(TUN_PFD).close();
+ if (verifyPacketSockFd) verify(PACKET_SOCK_PFD).close();
+ if (verifyRawSockFd) verify(RAW_SOCK_PFD).close();
+
+ // [2] Expect that unmodified TestDependencies can start clatd.
+ // Used to make sure that the above modified TestDependencies has really broken the
+ // clatd starting.
+ assertStartClat(new TestDependencies());
+ }
+
+ // The following testNotStartClat* tests verifies bunches of code for unwinding the
+ // failure if any.
+ @Test
+ public void testNotStartClatWithNativeFailureSelectIpv4Address() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public String selectIpv4Address(@NonNull String v4addr, int prefixlen)
+ throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
+ false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureGenerateIpv6Address() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public String generateIpv6Address(@NonNull String iface, @NonNull String v4,
+ @NonNull String prefix64) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
+ false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureCreateTunInterface() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int createTunInterface(@NonNull String tuniface) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
+ false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureDetectMtu() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int detectMtu(@NonNull String platSubnet, int platSuffix, int mark)
+ throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureOpenPacketSocket() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int openPacketSocket() throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureOpenRawSocket6() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int openRawSocket6(int mark) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ true /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureAddAnycastSetsockopt() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public void addAnycastSetsockopt(@NonNull FileDescriptor sock, String v6,
+ int ifindex) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureTagSocketAsClat() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public long tagSocketAsClat(@NonNull FileDescriptor sock) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureConfigurePacketSocket() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public void configurePacketSocket(@NonNull FileDescriptor sock, String v6,
+ int ifindex) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureStartClatd() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int startClatd(@NonNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6,
+ @NonNull FileDescriptor writesock6, @NonNull String iface,
+ @NonNull String pfx96, @NonNull String v4, @NonNull String v6)
+ throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
+ true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 11fbcb9..eb35469 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -45,6 +45,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
@@ -111,6 +112,7 @@
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
+import android.os.PowerWhitelistManager;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
@@ -129,6 +131,7 @@
import com.android.internal.net.VpnProfile;
import com.android.internal.util.HexDump;
import com.android.modules.utils.build.SdkLevel;
+import com.android.server.DeviceIdleInternal;
import com.android.server.IpSecService;
import com.android.server.vcn.util.PersistableBundleUtils;
import com.android.testutils.DevSdkIgnoreRule;
@@ -235,6 +238,7 @@
@Mock private ConnectivityManager mConnectivityManager;
@Mock private IpSecService mIpSecService;
@Mock private VpnProfileStore mVpnProfileStore;
+ @Mock DeviceIdleInternal mDeviceIdleInternal;
private final VpnProfile mVpnProfile;
private IpSecManager mIpSecManager;
@@ -408,6 +412,12 @@
disallow);
}
+ private void verifyPowerSaveTempWhitelistApp(String packageName) {
+ verify(mDeviceIdleInternal).addPowerSaveTempWhitelistApp(anyInt(), eq(packageName),
+ anyLong(), anyInt(), eq(false), eq(PowerWhitelistManager.REASON_VPN),
+ eq("VpnManager event"));
+ }
+
@Test
public void testGetAlwaysAndOnGetLockDown() throws Exception {
final Vpn vpn = createVpn(primaryUser.id);
@@ -1144,6 +1154,8 @@
verifyPlatformVpnIsActivated(TEST_VPN_PKG);
vpn.stopVpnProfile(TEST_VPN_PKG);
verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
+ verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
+ reset(mDeviceIdleInternal);
// CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
// errorCode won't be set.
verifyVpnManagerEvent(sessionKey1, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
@@ -1155,6 +1167,8 @@
verifyPlatformVpnIsActivated(TEST_VPN_PKG);
vpn.prepare(TEST_VPN_PKG, "com.new.vpn" /* newPackage */, TYPE_VPN_PLATFORM);
verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
+ verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
+ reset(mDeviceIdleInternal);
// CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
// errorCode won't be set.
verifyVpnManagerEvent(sessionKey2, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
@@ -1170,6 +1184,8 @@
// Enable VPN always-on for PKGS[1].
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
null /* lockdownAllowlist */));
+ verifyPowerSaveTempWhitelistApp(PKGS[1]);
+ reset(mDeviceIdleInternal);
verifyVpnManagerEvent(null /* sessionKey */,
VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
-1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
@@ -1178,6 +1194,8 @@
// Enable VPN lockdown for PKGS[1].
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true /* lockdown */,
null /* lockdownAllowlist */));
+ verifyPowerSaveTempWhitelistApp(PKGS[1]);
+ reset(mDeviceIdleInternal);
verifyVpnManagerEvent(null /* sessionKey */,
VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
-1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
@@ -1186,6 +1204,8 @@
// Disable VPN lockdown for PKGS[1].
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
null /* lockdownAllowlist */));
+ verifyPowerSaveTempWhitelistApp(PKGS[1]);
+ reset(mDeviceIdleInternal);
verifyVpnManagerEvent(null /* sessionKey */,
VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
-1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
@@ -1194,6 +1214,8 @@
// Disable VPN always-on.
assertTrue(vpn.setAlwaysOnPackage(null, false /* lockdown */,
null /* lockdownAllowlist */));
+ verifyPowerSaveTempWhitelistApp(PKGS[1]);
+ reset(mDeviceIdleInternal);
verifyVpnManagerEvent(null /* sessionKey */,
VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
-1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
@@ -1202,6 +1224,8 @@
// Enable VPN always-on for PKGS[1] again.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
null /* lockdownAllowlist */));
+ verifyPowerSaveTempWhitelistApp(PKGS[1]);
+ reset(mDeviceIdleInternal);
verifyVpnManagerEvent(null /* sessionKey */,
VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
-1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
@@ -1210,6 +1234,8 @@
// Enable VPN always-on for PKGS[2].
assertTrue(vpn.setAlwaysOnPackage(PKGS[2], false /* lockdown */,
null /* lockdownAllowlist */));
+ verifyPowerSaveTempWhitelistApp(PKGS[2]);
+ reset(mDeviceIdleInternal);
// PKGS[1] is replaced with PKGS[2].
// Pass 2 VpnProfileState objects to verifyVpnManagerEvent(), the first one is sent to
// PKGS[1] to notify PKGS[1] that the VPN always-on is disabled, the second one is sent to
@@ -1310,6 +1336,8 @@
final IkeSessionCallback ikeCb = captor.getValue();
ikeCb.onClosedWithException(exception);
+ verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
+ reset(mDeviceIdleInternal);
verifyVpnManagerEvent(sessionKey, category, errorType, errorCode, null /* profileState */);
if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
@@ -1532,7 +1560,7 @@
}
}
- private static final class TestDeps extends Vpn.Dependencies {
+ private final class TestDeps extends Vpn.Dependencies {
public final CompletableFuture<String[]> racoonArgs = new CompletableFuture();
public final CompletableFuture<String[]> mtpdArgs = new CompletableFuture();
public final File mStateFile;
@@ -1661,6 +1689,11 @@
@Override
public void setBlocking(FileDescriptor fd, boolean blocking) {}
+
+ @Override
+ public DeviceIdleInternal getDeviceIdleInternal() {
+ return mDeviceIdleInternal;
+ }
}
/**
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java b/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java
new file mode 100644
index 0000000..a9f80ea
--- /dev/null
+++ b/tests/unit/java/com/android/server/ethernet/EthernetConfigStoreTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.ethernet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.net.InetAddresses;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.LinkAddress;
+import android.net.ProxyInfo;
+import android.net.StaticIpConfiguration;
+import android.util.ArrayMap;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class EthernetConfigStoreTest {
+ private static final LinkAddress LINKADDR = new LinkAddress("192.168.1.100/25");
+ private static final InetAddress GATEWAY = InetAddresses.parseNumericAddress("192.168.1.1");
+ private static final InetAddress DNS1 = InetAddresses.parseNumericAddress("8.8.8.8");
+ private static final InetAddress DNS2 = InetAddresses.parseNumericAddress("8.8.4.4");
+ private static final StaticIpConfiguration STATIC_IP_CONFIG =
+ new StaticIpConfiguration.Builder()
+ .setIpAddress(LINKADDR)
+ .setGateway(GATEWAY)
+ .setDnsServers(new ArrayList<InetAddress>(
+ List.of(DNS1, DNS2)))
+ .build();
+ private static final ProxyInfo PROXY_INFO = ProxyInfo.buildDirectProxy("test", 8888);
+ private static final IpConfiguration APEX_IP_CONFIG =
+ new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
+ private static final IpConfiguration LEGACY_IP_CONFIG =
+ new IpConfiguration(IpAssignment.STATIC, ProxySettings.STATIC, STATIC_IP_CONFIG,
+ PROXY_INFO);
+
+ private EthernetConfigStore mEthernetConfigStore;
+ private File mApexTestDir;
+ private File mLegacyTestDir;
+ private File mApexConfigFile;
+ private File mLegacyConfigFile;
+
+ private void createTestDir() {
+ final Context context = InstrumentationRegistry.getContext();
+ final File baseDir = context.getFilesDir();
+ mApexTestDir = new File(baseDir.getPath() + "/apex");
+ mApexTestDir.mkdirs();
+
+ mLegacyTestDir = new File(baseDir.getPath() + "/legacy");
+ mLegacyTestDir.mkdirs();
+ }
+
+ @Before
+ public void setUp() {
+ createTestDir();
+ mEthernetConfigStore = new EthernetConfigStore();
+ }
+
+ @After
+ public void tearDown() {
+ mApexTestDir.delete();
+ mLegacyTestDir.delete();
+ }
+
+ private void assertConfigFileExist(final String filepath) {
+ assertTrue(new File(filepath).exists());
+ }
+
+ /** Wait for the delayed write operation completes. */
+ private void waitForMs(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (final InterruptedException e) {
+ fail("Thread was interrupted");
+ }
+ }
+
+ @Test
+ public void testWriteIpConfigToApexFilePathAndRead() throws Exception {
+ // Write the config file to the apex file path, pretend the config file exits and
+ // check if IP config should be read from apex file path.
+ mApexConfigFile = new File(mApexTestDir.getPath(), "test.txt");
+ mEthernetConfigStore.write("eth0", APEX_IP_CONFIG, mApexConfigFile.getPath());
+ waitForMs(50);
+
+ mEthernetConfigStore.read(mApexTestDir.getPath(), mLegacyTestDir.getPath(), "/test.txt");
+ final ArrayMap<String, IpConfiguration> ipConfigurations =
+ mEthernetConfigStore.getIpConfigurations();
+ assertEquals(APEX_IP_CONFIG, ipConfigurations.get("eth0"));
+
+ mApexConfigFile.delete();
+ }
+
+ @Test
+ public void testWriteIpConfigToLegacyFilePathAndRead() throws Exception {
+ // Write the config file to the legacy file path, pretend the config file exits and
+ // check if IP config should be read from legacy file path.
+ mLegacyConfigFile = new File(mLegacyTestDir, "test.txt");
+ mEthernetConfigStore.write("0", LEGACY_IP_CONFIG, mLegacyConfigFile.getPath());
+ waitForMs(50);
+
+ mEthernetConfigStore.read(mApexTestDir.getPath(), mLegacyTestDir.getPath(), "/test.txt");
+ final ArrayMap<String, IpConfiguration> ipConfigurations =
+ mEthernetConfigStore.getIpConfigurations();
+ assertEquals(LEGACY_IP_CONFIG, ipConfigurations.get("0"));
+
+ // Check the same config file in apex file path is created.
+ assertConfigFileExist(mApexTestDir.getPath() + "/test.txt");
+
+ final File apexConfigFile = new File(mApexTestDir.getPath() + "/test.txt");
+ apexConfigFile.delete();
+ mLegacyConfigFile.delete();
+ }
+}
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
index dfb4fcc..4f849d2 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
@@ -16,8 +16,6 @@
package com.android.server.ethernet;
-import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
@@ -32,7 +30,6 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -51,20 +48,22 @@
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
+import android.net.NetworkProvider.NetworkOfferCallback;
import android.net.NetworkRequest;
import android.net.StaticIpConfiguration;
import android.net.ip.IpClientCallbacks;
import android.net.ip.IpClientManager;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.test.TestLooper;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.InterfaceParams;
import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.After;
import org.junit.Before;
@@ -79,8 +78,9 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-@RunWith(AndroidJUnit4.class)
@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class EthernetNetworkFactoryTest {
private static final int TIMEOUT_MS = 2_000;
private static final String TEST_IFACE = "test123";
@@ -99,6 +99,7 @@
@Mock private EthernetNetworkAgent mNetworkAgent;
@Mock private InterfaceParams mInterfaceParams;
@Mock private Network mMockNetwork;
+ @Mock private NetworkProvider mNetworkProvider;
@Before
public void setUp() throws Exception {
@@ -112,7 +113,7 @@
private void initEthernetNetworkFactory() {
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
- mNetFactory = new EthernetNetworkFactory(mHandler, mContext, mDeps);
+ mNetFactory = new EthernetNetworkFactory(mHandler, mContext, mNetworkProvider, mDeps);
}
private void setupNetworkAgentMock() {
@@ -239,9 +240,16 @@
mNetFactory.addInterface(iface, HW_ADDR, ipConfig,
createInterfaceCapsBuilder(transportType).build());
assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER));
+
+ ArgumentCaptor<NetworkOfferCallback> captor = ArgumentCaptor.forClass(
+ NetworkOfferCallback.class);
+ verify(mNetworkProvider).registerNetworkOffer(any(), any(), any(), captor.capture());
+ captor.getValue().onNetworkNeeded(createDefaultRequest());
+
verifyStart(ipConfig);
clearInvocations(mDeps);
clearInvocations(mIpClient);
+ clearInvocations(mNetworkProvider);
}
// creates a provisioned interface
@@ -281,29 +289,15 @@
// To create an unprovisioned interface, provision and then "stop" it, i.e. stop its
// NetworkAgent and IpClient. One way this can be done is by provisioning an interface and
// then calling onNetworkUnwanted.
- createAndVerifyProvisionedInterface(iface);
-
- mNetworkAgent.getCallbacks().onNetworkUnwanted();
- mLooper.dispatchAll();
- verifyStop();
+ mNetFactory.addInterface(iface, HW_ADDR, createDefaultIpConfig(),
+ createInterfaceCapsBuilder(NetworkCapabilities.TRANSPORT_ETHERNET).build());
+ assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER));
clearInvocations(mIpClient);
clearInvocations(mNetworkAgent);
}
@Test
- public void testAcceptRequest() throws Exception {
- initEthernetNetworkFactory();
- createInterfaceUndergoingProvisioning(TEST_IFACE);
- assertTrue(mNetFactory.acceptRequest(createDefaultRequest()));
-
- NetworkRequest wifiRequest = createDefaultRequestBuilder()
- .removeTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
- .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
- assertFalse(mNetFactory.acceptRequest(wifiRequest));
- }
-
- @Test
public void testUpdateInterfaceLinkStateForActiveProvisioningInterface() throws Exception {
initEthernetNetworkFactory();
createInterfaceUndergoingProvisioning(TEST_IFACE);
@@ -378,36 +372,6 @@
}
@Test
- public void testNeedNetworkForOnProvisionedInterface() throws Exception {
- initEthernetNetworkFactory();
- createAndVerifyProvisionedInterface(TEST_IFACE);
- mNetFactory.needNetworkFor(createDefaultRequest());
- verify(mIpClient, never()).startProvisioning(any());
- }
-
- @Test
- public void testNeedNetworkForOnUnprovisionedInterface() throws Exception {
- initEthernetNetworkFactory();
- createUnprovisionedInterface(TEST_IFACE);
- mNetFactory.needNetworkFor(createDefaultRequest());
- verify(mIpClient).startProvisioning(any());
-
- triggerOnProvisioningSuccess();
- verifyNetworkAgentRegistersAndConnects();
- }
-
- @Test
- public void testNeedNetworkForOnInterfaceUndergoingProvisioning() throws Exception {
- initEthernetNetworkFactory();
- createInterfaceUndergoingProvisioning(TEST_IFACE);
- mNetFactory.needNetworkFor(createDefaultRequest());
- verify(mIpClient, never()).startProvisioning(any());
-
- triggerOnProvisioningSuccess();
- verifyNetworkAgentRegistersAndConnects();
- }
-
- @Test
public void testProvisioningLoss() throws Exception {
initEthernetNetworkFactory();
when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
@@ -441,31 +405,6 @@
}
@Test
- public void testIpClientIsNotStartedWhenLinkIsDown() throws Exception {
- initEthernetNetworkFactory();
- createUnprovisionedInterface(TEST_IFACE);
- mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER);
-
- mNetFactory.needNetworkFor(createDefaultRequest());
-
- verify(mDeps, never()).makeIpClient(any(), any(), any());
-
- // BUG(b/191854824): requesting a network with a specifier (Android Auto use case) should
- // not start an IpClient when the link is down, but fixing this may make matters worse by
- // tiggering b/197548738.
- NetworkRequest specificNetRequest = new NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
- .setNetworkSpecifier(new EthernetNetworkSpecifier(TEST_IFACE))
- .build();
- mNetFactory.needNetworkFor(specificNetRequest);
- mNetFactory.releaseNetworkFor(specificNetRequest);
-
- mNetFactory.updateInterfaceLinkState(TEST_IFACE, true, NULL_LISTENER);
- // TODO: change to once when b/191854824 is fixed.
- verify(mDeps, times(2)).makeIpClient(any(), eq(TEST_IFACE), any());
- }
-
- @Test
public void testLinkPropertiesChanged() throws Exception {
initEthernetNetworkFactory();
createAndVerifyProvisionedInterface(TEST_IFACE);
@@ -669,7 +608,6 @@
assertEquals(listener.expectOnResult(), TEST_IFACE);
}
- @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
@Test
public void testUpdateInterfaceAbortsOnConcurrentRemoveInterface() throws Exception {
initEthernetNetworkFactory();
@@ -678,7 +616,6 @@
() -> mNetFactory.removeInterface(TEST_IFACE));
}
- @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
@Test
public void testUpdateInterfaceAbortsOnConcurrentUpdateInterfaceLinkState() throws Exception {
initEthernetNetworkFactory();
@@ -687,7 +624,6 @@
() -> mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER));
}
- @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
@Test
public void testUpdateInterfaceCallsListenerCorrectlyOnConcurrentRequests() throws Exception {
initEthernetNetworkFactory();
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java b/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
index dd1f1ed..b2b9f2c 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
@@ -20,12 +20,12 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
-
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -35,23 +35,25 @@
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.EthernetNetworkUpdateRequest;
+import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.IpConfiguration;
import android.net.NetworkCapabilities;
+import android.os.Build;
import android.os.Handler;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-@RunWith(AndroidJUnit4.class)
@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class EthernetServiceImplTest {
private static final String TEST_IFACE = "test123";
private static final EthernetNetworkUpdateRequest UPDATE_REQUEST =
@@ -69,14 +71,17 @@
.build();
private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null;
private EthernetServiceImpl mEthernetServiceImpl;
- @Mock private Context mContext;
- @Mock private Handler mHandler;
- @Mock private EthernetTracker mEthernetTracker;
- @Mock private PackageManager mPackageManager;
+ private Context mContext;
+ private Handler mHandler;
+ private EthernetTracker mEthernetTracker;
+ private PackageManager mPackageManager;
@Before
public void setup() {
- MockitoAnnotations.initMocks(this);
+ mContext = mock(Context.class);
+ mHandler = mock(Handler.class);
+ mEthernetTracker = mock(EthernetTracker.class);
+ mPackageManager = mock(PackageManager.class);
doReturn(mPackageManager).when(mContext).getPackageManager();
mEthernetServiceImpl = new EthernetServiceImpl(mContext, mHandler, mEthernetTracker);
mEthernetServiceImpl.mStarted.set(true);
@@ -111,18 +116,18 @@
}
@Test
- public void testConnectNetworkRejectsWhenEthNotStarted() {
+ public void testEnableInterfaceRejectsWhenEthNotStarted() {
mEthernetServiceImpl.mStarted.set(false);
assertThrows(IllegalStateException.class, () -> {
- mEthernetServiceImpl.connectNetwork("" /* iface */, null /* listener */);
+ mEthernetServiceImpl.enableInterface("" /* iface */, null /* listener */);
});
}
@Test
- public void testDisconnectNetworkRejectsWhenEthNotStarted() {
+ public void testDisableInterfaceRejectsWhenEthNotStarted() {
mEthernetServiceImpl.mStarted.set(false);
assertThrows(IllegalStateException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork("" /* iface */, null /* listener */);
+ mEthernetServiceImpl.disableInterface("" /* iface */, null /* listener */);
});
}
@@ -134,16 +139,16 @@
}
@Test
- public void testConnectNetworkRejectsNullIface() {
+ public void testEnableInterfaceRejectsNullIface() {
assertThrows(NullPointerException.class, () -> {
- mEthernetServiceImpl.connectNetwork(null /* iface */, NULL_LISTENER);
+ mEthernetServiceImpl.enableInterface(null /* iface */, NULL_LISTENER);
});
}
@Test
- public void testDisconnectNetworkRejectsNullIface() {
+ public void testDisableInterfaceRejectsNullIface() {
assertThrows(NullPointerException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork(null /* iface */, NULL_LISTENER);
+ mEthernetServiceImpl.disableInterface(null /* iface */, NULL_LISTENER);
});
}
@@ -165,22 +170,6 @@
eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getNetworkCapabilities()), isNull());
}
- @Test
- public void testConnectNetworkRejectsWithoutAutomotiveFeature() {
- toggleAutomotiveFeature(false);
- assertThrows(UnsupportedOperationException.class, () -> {
- mEthernetServiceImpl.connectNetwork("" /* iface */, NULL_LISTENER);
- });
- }
-
- @Test
- public void testDisconnectNetworkRejectsWithoutAutomotiveFeature() {
- toggleAutomotiveFeature(false);
- assertThrows(UnsupportedOperationException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork("" /* iface */, NULL_LISTENER);
- });
- }
-
private void denyManageEthPermission() {
doThrow(new SecurityException("")).when(mContext)
.enforceCallingOrSelfPermission(
@@ -202,18 +191,18 @@
}
@Test
- public void testConnectNetworkRejectsWithoutManageEthPermission() {
+ public void testEnableInterfaceRejectsWithoutManageEthPermission() {
denyManageEthPermission();
assertThrows(SecurityException.class, () -> {
- mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
});
}
@Test
- public void testDisconnectNetworkRejectsWithoutManageEthPermission() {
+ public void testDisableInterfaceRejectsWithoutManageEthPermission() {
denyManageEthPermission();
assertThrows(SecurityException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
});
}
@@ -231,20 +220,20 @@
}
@Test
- public void testConnectNetworkRejectsTestRequestWithoutTestPermission() {
+ public void testEnableInterfaceRejectsTestRequestWithoutTestPermission() {
enableTestInterface();
denyManageTestNetworksPermission();
assertThrows(SecurityException.class, () -> {
- mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
});
}
@Test
- public void testDisconnectNetworkRejectsTestRequestWithoutTestPermission() {
+ public void testDisableInterfaceRejectsTestRequestWithoutTestPermission() {
enableTestInterface();
denyManageTestNetworksPermission();
assertThrows(SecurityException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
});
}
@@ -258,15 +247,15 @@
}
@Test
- public void testConnectNetwork() {
- mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).connectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
+ public void testEnableInterface() {
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).enableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
}
@Test
- public void testDisconnectNetwork() {
- mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).disconnectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
+ public void testDisableInterface() {
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).disableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
}
@Test
@@ -324,23 +313,23 @@
}
@Test
- public void testConnectNetworkForTestRequestDoesNotRequireAutoOrNetPermission() {
+ public void testEnableInterfaceForTestRequestDoesNotRequireNetPermission() {
enableTestInterface();
toggleAutomotiveFeature(false);
denyManageEthPermission();
- mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).connectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).enableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
}
@Test
- public void testDisconnectNetworkForTestRequestDoesNotRequireAutoOrNetPermission() {
+ public void testDisableInterfaceForTestRequestDoesNotRequireAutoOrNetPermission() {
enableTestInterface();
toggleAutomotiveFeature(false);
denyManageEthPermission();
- mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).disconnectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).disableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
}
private void denyPermissions(String... permissions) {
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
index b1831c4..38094ae 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -28,47 +28,53 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.content.res.Resources;
import android.net.EthernetManager;
-import android.net.InetAddresses;
-import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.IEthernetServiceListener;
import android.net.INetd;
+import android.net.INetworkInterfaceOutcomeReceiver;
+import android.net.InetAddresses;
+import android.net.InterfaceConfigurationParcel;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
import android.net.IpConfiguration.ProxySettings;
-import android.net.InterfaceConfigurationParcel;
import android.net.LinkAddress;
import android.net.NetworkCapabilities;
import android.net.StaticIpConfiguration;
+import android.os.Build;
import android.os.HandlerThread;
import android.os.RemoteException;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-import com.android.connectivity.resources.R;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.net.InetAddress;
import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class EthernetTrackerTest {
private static final String TEST_IFACE = "test123";
private static final int TIMEOUT_MS = 1_000;
@@ -351,8 +357,8 @@
}
@Test
- public void testConnectNetworkCorrectlyCallsFactory() {
- tracker.connectNetwork(TEST_IFACE, NULL_LISTENER);
+ public void testEnableInterfaceCorrectlyCallsFactory() {
+ tracker.enableInterface(TEST_IFACE, NULL_LISTENER);
waitForIdle();
verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(true /* up */),
@@ -360,8 +366,8 @@
}
@Test
- public void testDisconnectNetworkCorrectlyCallsFactory() {
- tracker.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
+ public void testDisableInterfaceCorrectlyCallsFactory() {
+ tracker.disableInterface(TEST_IFACE, NULL_LISTENER);
waitForIdle();
verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(false /* up */),
@@ -410,22 +416,41 @@
IpConfiguration configuration) { }
}
+ private InterfaceConfigurationParcel createMockedIfaceParcel(final String ifname,
+ final String hwAddr) {
+ final InterfaceConfigurationParcel ifaceParcel = new InterfaceConfigurationParcel();
+ ifaceParcel.ifName = ifname;
+ ifaceParcel.hwAddr = hwAddr;
+ ifaceParcel.flags = new String[] {INetd.IF_STATE_UP};
+ return ifaceParcel;
+ }
+
@Test
public void testListenEthernetStateChange() throws Exception {
- final String testIface = "testtap123";
- final String testHwAddr = "11:22:33:44:55:66";
- final InterfaceConfigurationParcel ifaceParcel = new InterfaceConfigurationParcel();
- ifaceParcel.ifName = testIface;
- ifaceParcel.hwAddr = testHwAddr;
- ifaceParcel.flags = new String[] {INetd.IF_STATE_UP};
-
tracker.setIncludeTestInterfaces(true);
waitForIdle();
+ final String testIface = "testtap123";
+ final String testHwAddr = "11:22:33:44:55:66";
+ final InterfaceConfigurationParcel ifaceParcel = createMockedIfaceParcel(testIface,
+ testHwAddr);
when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface});
when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(ifaceParcel);
doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean());
- doReturn(EthernetManager.STATE_LINK_UP).when(mFactory).getInterfaceState(eq(testIface));
+
+ final AtomicBoolean ifaceUp = new AtomicBoolean(true);
+ doAnswer(inv -> ifaceUp.get()).when(mFactory).hasInterface(testIface);
+ doAnswer(inv ->
+ ifaceUp.get() ? EthernetManager.STATE_LINK_UP : EthernetManager.STATE_ABSENT)
+ .when(mFactory).getInterfaceState(testIface);
+ doAnswer(inv -> {
+ ifaceUp.set(true);
+ return null;
+ }).when(mFactory).addInterface(eq(testIface), eq(testHwAddr), any(), any());
+ doAnswer(inv -> {
+ ifaceUp.set(false);
+ return null;
+ }).when(mFactory).removeInterface(testIface);
final EthernetStateListener listener = spy(new EthernetStateListener());
tracker.addListener(listener, true /* canUseRestrictedNetworks */);
@@ -436,7 +461,6 @@
verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_ENABLED));
reset(listener);
- doReturn(EthernetManager.STATE_ABSENT).when(mFactory).getInterfaceState(eq(testIface));
tracker.setEthernetEnabled(false);
waitForIdle();
verify(mFactory).removeInterface(eq(testIface));
@@ -445,7 +469,6 @@
anyInt(), any());
reset(listener);
- doReturn(EthernetManager.STATE_LINK_UP).when(mFactory).getInterfaceState(eq(testIface));
tracker.setEthernetEnabled(true);
waitForIdle();
verify(mFactory).addInterface(eq(testIface), eq(testHwAddr), any(), any());
@@ -453,4 +476,43 @@
verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP),
anyInt(), any());
}
+
+ @Test
+ public void testListenEthernetStateChange_unsolicitedEventListener() throws Exception {
+ when(mNetd.interfaceGetList()).thenReturn(new String[] {});
+ doReturn(new String[] {}).when(mFactory).getAvailableInterfaces(anyBoolean());
+
+ tracker.setIncludeTestInterfaces(true);
+ tracker.start();
+
+ final ArgumentCaptor<EthernetTracker.InterfaceObserver> captor =
+ ArgumentCaptor.forClass(EthernetTracker.InterfaceObserver.class);
+ verify(mNetd, timeout(TIMEOUT_MS)).registerUnsolicitedEventListener(captor.capture());
+ final EthernetTracker.InterfaceObserver observer = captor.getValue();
+
+ tracker.setEthernetEnabled(false);
+ waitForIdle();
+ reset(mFactory);
+ reset(mNetd);
+
+ final String testIface = "testtap1";
+ observer.onInterfaceAdded(testIface);
+ verify(mFactory, never()).addInterface(eq(testIface), anyString(), any(), any());
+ observer.onInterfaceRemoved(testIface);
+ verify(mFactory, never()).removeInterface(eq(testIface));
+
+ final String testHwAddr = "11:22:33:44:55:66";
+ final InterfaceConfigurationParcel testIfaceParce =
+ createMockedIfaceParcel(testIface, testHwAddr);
+ when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface});
+ when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(testIfaceParce);
+ doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean());
+ tracker.setEthernetEnabled(true);
+ waitForIdle();
+ reset(mFactory);
+
+ final String testIface2 = "testtap2";
+ observer.onInterfaceRemoved(testIface2);
+ verify(mFactory, timeout(TIMEOUT_MS)).removeInterface(eq(testIface2));
+ }
}
diff --git a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
index 987b7b7..c6852d1 100644
--- a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
+++ b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
@@ -24,16 +24,18 @@
import android.content.Context;
import android.net.INetd;
import android.net.MacAddress;
+import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.Struct.U32;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
@@ -42,8 +44,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-@RunWith(AndroidJUnit4.class)
@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public final class BpfInterfaceMapUpdaterTest {
private static final int TEST_INDEX = 1;
private static final int TEST_INDEX2 = 2;
diff --git a/tests/unit/java/com/android/server/net/IpConfigStoreTest.java b/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
index e9a5309..4adc999 100644
--- a/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
+++ b/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
+import android.content.Context;
import android.net.InetAddresses;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
@@ -27,9 +28,15 @@
import android.net.LinkAddress;
import android.net.ProxyInfo;
import android.net.StaticIpConfiguration;
+import android.os.Build;
+import android.os.HandlerThread;
import android.util.ArrayMap;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.HandlerUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,17 +44,21 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
* Unit tests for {@link IpConfigStore}
*/
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpConfigStoreTest {
+ private static final int TIMEOUT_MS = 2_000;
private static final int KEY_CONFIG = 17;
private static final String IFACE_1 = "eth0";
private static final String IFACE_2 = "eth1";
@@ -56,6 +67,22 @@
private static final String DNS_IP_ADDR_1 = "1.2.3.4";
private static final String DNS_IP_ADDR_2 = "5.6.7.8";
+ private static final ArrayList<InetAddress> DNS_SERVERS = new ArrayList<>(List.of(
+ InetAddresses.parseNumericAddress(DNS_IP_ADDR_1),
+ InetAddresses.parseNumericAddress(DNS_IP_ADDR_2)));
+ private static final StaticIpConfiguration STATIC_IP_CONFIG_1 =
+ new StaticIpConfiguration.Builder()
+ .setIpAddress(new LinkAddress(IP_ADDR_1))
+ .setDnsServers(DNS_SERVERS)
+ .build();
+ private static final StaticIpConfiguration STATIC_IP_CONFIG_2 =
+ new StaticIpConfiguration.Builder()
+ .setIpAddress(new LinkAddress(IP_ADDR_2))
+ .setDnsServers(DNS_SERVERS)
+ .build();
+ private static final ProxyInfo PROXY_INFO =
+ ProxyInfo.buildDirectProxy("10.10.10.10", 88, Arrays.asList("host1", "host2"));
+
@Test
public void backwardCompatibility2to3() throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
@@ -79,40 +106,73 @@
@Test
public void staticIpMultiNetworks() throws Exception {
- final ArrayList<InetAddress> dnsServers = new ArrayList<>();
- dnsServers.add(InetAddresses.parseNumericAddress(DNS_IP_ADDR_1));
- dnsServers.add(InetAddresses.parseNumericAddress(DNS_IP_ADDR_2));
- final StaticIpConfiguration staticIpConfiguration1 = new StaticIpConfiguration.Builder()
- .setIpAddress(new LinkAddress(IP_ADDR_1))
- .setDnsServers(dnsServers).build();
- final StaticIpConfiguration staticIpConfiguration2 = new StaticIpConfiguration.Builder()
- .setIpAddress(new LinkAddress(IP_ADDR_2))
- .setDnsServers(dnsServers).build();
+ final IpConfiguration expectedConfig1 = newIpConfiguration(IpAssignment.STATIC,
+ ProxySettings.STATIC, STATIC_IP_CONFIG_1, PROXY_INFO);
+ final IpConfiguration expectedConfig2 = newIpConfiguration(IpAssignment.STATIC,
+ ProxySettings.STATIC, STATIC_IP_CONFIG_2, PROXY_INFO);
- ProxyInfo proxyInfo =
- ProxyInfo.buildDirectProxy("10.10.10.10", 88, Arrays.asList("host1", "host2"));
-
- IpConfiguration expectedConfig1 = newIpConfiguration(IpAssignment.STATIC,
- ProxySettings.STATIC, staticIpConfiguration1, proxyInfo);
- IpConfiguration expectedConfig2 = newIpConfiguration(IpAssignment.STATIC,
- ProxySettings.STATIC, staticIpConfiguration2, proxyInfo);
-
- ArrayMap<String, IpConfiguration> expectedNetworks = new ArrayMap<>();
+ final ArrayMap<String, IpConfiguration> expectedNetworks = new ArrayMap<>();
expectedNetworks.put(IFACE_1, expectedConfig1);
expectedNetworks.put(IFACE_2, expectedConfig2);
- MockedDelayedDiskWrite writer = new MockedDelayedDiskWrite();
- IpConfigStore store = new IpConfigStore(writer);
+ final MockedDelayedDiskWrite writer = new MockedDelayedDiskWrite();
+ final IpConfigStore store = new IpConfigStore(writer);
store.writeIpConfigurations("file/path/not/used/", expectedNetworks);
- InputStream in = new ByteArrayInputStream(writer.mByteStream.toByteArray());
- ArrayMap<String, IpConfiguration> actualNetworks = IpConfigStore.readIpConfigurations(in);
+ final InputStream in = new ByteArrayInputStream(writer.mByteStream.toByteArray());
+ final ArrayMap<String, IpConfiguration> actualNetworks =
+ IpConfigStore.readIpConfigurations(in);
assertNotNull(actualNetworks);
assertEquals(2, actualNetworks.size());
assertEquals(expectedNetworks.get(IFACE_1), actualNetworks.get(IFACE_1));
assertEquals(expectedNetworks.get(IFACE_2), actualNetworks.get(IFACE_2));
}
+ @Test
+ public void readIpConfigurationFromFilePath() throws Exception {
+ final HandlerThread testHandlerThread = new HandlerThread("IpConfigStoreTest");
+ final DelayedDiskWrite.Dependencies dependencies =
+ new DelayedDiskWrite.Dependencies() {
+ @Override
+ public HandlerThread makeHandlerThread() {
+ return testHandlerThread;
+ }
+ @Override
+ public void quitHandlerThread(HandlerThread handlerThread) {
+ testHandlerThread.quitSafely();
+ }
+ };
+
+ final IpConfiguration ipConfig = newIpConfiguration(IpAssignment.STATIC,
+ ProxySettings.STATIC, STATIC_IP_CONFIG_1, PROXY_INFO);
+ final ArrayMap<String, IpConfiguration> expectedNetworks = new ArrayMap<>();
+ expectedNetworks.put(IFACE_1, ipConfig);
+
+ // Write IP config to specific file path and read it later.
+ final Context context = InstrumentationRegistry.getContext();
+ final File configFile = new File(context.getFilesDir().getPath(),
+ "IpConfigStoreTest-ipconfig.txt");
+ final DelayedDiskWrite writer = new DelayedDiskWrite(dependencies);
+ final IpConfigStore store = new IpConfigStore(writer);
+ store.writeIpConfigurations(configFile.getPath(), expectedNetworks);
+ HandlerUtils.waitForIdle(testHandlerThread, TIMEOUT_MS);
+
+ // Read IP config from the file path.
+ final ArrayMap<String, IpConfiguration> actualNetworks =
+ IpConfigStore.readIpConfigurations(configFile.getPath());
+ assertNotNull(actualNetworks);
+ assertEquals(1, actualNetworks.size());
+ assertEquals(expectedNetworks.get(IFACE_1), actualNetworks.get(IFACE_1));
+
+ // Return an empty array when reading IP configuration from an non-exist config file.
+ final ArrayMap<String, IpConfiguration> emptyNetworks =
+ IpConfigStore.readIpConfigurations("/dev/null");
+ assertNotNull(emptyNetworks);
+ assertEquals(0, emptyNetworks.size());
+
+ configFile.delete();
+ }
+
private IpConfiguration newIpConfiguration(IpAssignment ipAssignment,
ProxySettings proxySettings, StaticIpConfiguration staticIpConfig, ProxyInfo info) {
final IpConfiguration config = new IpConfiguration();
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 79744b1..5400a00 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -29,6 +29,7 @@
import static android.net.NetworkStats.UID_ALL;
import static com.android.server.net.NetworkStatsFactory.kernelToTag;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -38,7 +39,6 @@
import android.net.NetworkStats;
import android.net.TrafficStats;
import android.net.UnderlyingNetworkInfo;
-import android.os.Build;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -67,7 +67,7 @@
/** Tests for {@link NetworkStatsFactory}. */
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public class NetworkStatsFactoryTest extends NetworkStatsBaseTest {
private static final String CLAT_PREFIX = "v4-";
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index e8c9637..5747e10 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -274,8 +274,12 @@
mStatsObservers.unregister(request, UID_BLUE);
waitForObserverToIdle();
-
Mockito.verifyZeroInteractions(mUsageCallbackBinder);
+
+ // Verify that system uid can unregister for other uids.
+ mStatsObservers.unregister(request, Process.SYSTEM_UID);
+ waitForObserverToIdle();
+ mUsageCallback.expectOnCallbackReleased(request);
}
private NetworkIdentitySet makeTestIdentSet() {
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index ceeb997..f1820b3 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.app.usage.NetworkStatsManager.PREFIX_DEV;
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.EXTRA_UID;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -56,6 +57,9 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.net.TrafficStats.UID_REMOVED;
import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -77,6 +81,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -96,6 +101,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkStateSnapshot;
import android.net.NetworkStats;
+import android.net.NetworkStatsCollection;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TelephonyNetworkSpecifier;
@@ -104,6 +110,7 @@
import android.net.UnderlyingNetworkInfo;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.wifi.WifiInfo;
+import android.os.DropBoxManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -112,11 +119,13 @@
import android.provider.Settings;
import android.system.ErrnoException;
import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.internal.util.FileRotator;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.LocationPermissionChecker;
@@ -131,6 +140,16 @@
import com.android.testutils.TestBpfMap;
import com.android.testutils.TestableNetworkStatsProviderBinder;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Clock;
+import java.time.ZoneOffset;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import libcore.testing.io.TestIoUtils;
import org.junit.After;
@@ -142,13 +161,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.io.File;
-import java.time.Clock;
-import java.time.ZoneOffset;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
/**
* Tests for {@link NetworkStatsService}.
*
@@ -187,6 +199,7 @@
private long mElapsedRealtime;
private File mStatsDir;
+ private File mLegacyStatsDir;
private MockContext mServiceContext;
private @Mock TelephonyManager mTelephonyManager;
private static @Mock WifiInfo sWifiInfo;
@@ -220,6 +233,12 @@
private ContentObserver mContentObserver;
private Handler mHandler;
private TetheringManager.TetheringEventCallback mTetheringEventCallback;
+ private Map<String, NetworkStatsCollection> mPlatformNetworkStatsCollection =
+ new ArrayMap<String, NetworkStatsCollection>();
+ private boolean mStoreFilesInApexData = false;
+ private int mImportLegacyTargetAttempts = 0;
+ private @Mock PersistentInt mImportLegacyAttemptsCounter;
+ private @Mock PersistentInt mImportLegacySuccessesCounter;
private class MockContext extends BroadcastInterceptingContext {
private final Context mBaseContext;
@@ -286,6 +305,8 @@
any(), any(), anyInt(), anyBoolean(), any())).thenReturn(true);
when(sWifiInfo.getNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY);
mStatsDir = TestIoUtils.createTemporaryDirectory(getClass().getSimpleName());
+ mLegacyStatsDir = TestIoUtils.createTemporaryDirectory(
+ getClass().getSimpleName() + "-legacy");
PowerManager powerManager = (PowerManager) mServiceContext.getSystemService(
Context.POWER_SERVICE);
@@ -295,8 +316,7 @@
mHandlerThread = new HandlerThread("HandlerThread");
final NetworkStatsService.Dependencies deps = makeDependencies();
mService = new NetworkStatsService(mServiceContext, mNetd, mAlarmManager, wakeLock,
- mClock, mSettings, mStatsFactory, new NetworkStatsObservers(), mStatsDir,
- getBaseDir(mStatsDir), deps);
+ mClock, mSettings, mStatsFactory, new NetworkStatsObservers(), deps);
mElapsedRealtime = 0L;
@@ -339,6 +359,44 @@
private NetworkStatsService.Dependencies makeDependencies() {
return new NetworkStatsService.Dependencies() {
@Override
+ public File getLegacyStatsDir() {
+ return mLegacyStatsDir;
+ }
+
+ @Override
+ public File getOrCreateStatsDir() {
+ return mStatsDir;
+ }
+
+ @Override
+ public boolean getStoreFilesInApexData() {
+ return mStoreFilesInApexData;
+ }
+
+ @Override
+ public int getImportLegacyTargetAttempts() {
+ return mImportLegacyTargetAttempts;
+ }
+
+ @Override
+ public PersistentInt createImportLegacyAttemptsCounter(
+ @androidx.annotation.NonNull Path path) {
+ return mImportLegacyAttemptsCounter;
+ }
+
+ @Override
+ public PersistentInt createImportLegacySuccessesCounter(
+ @androidx.annotation.NonNull Path path) {
+ return mImportLegacySuccessesCounter;
+ }
+
+ @Override
+ public NetworkStatsCollection readPlatformCollection(
+ @NonNull String prefix, long bucketDuration) {
+ return mPlatformNetworkStatsCollection.get(prefix);
+ }
+
+ @Override
public HandlerThread makeHandlerThread() {
return mHandlerThread;
}
@@ -1704,10 +1762,108 @@
assertNetworkTotal(sTemplateImsi1, 0L, 0L, 0L, 0L, 0);
}
- private static File getBaseDir(File statsDir) {
- File baseDir = new File(statsDir, "netstats");
- baseDir.mkdirs();
- return baseDir;
+ /**
+ * Verify the service will perform data migration process can be controlled by the device flag.
+ */
+ @Test
+ public void testDataMigration() throws Exception {
+ assertStatsFilesExist(false);
+ expectDefaultSettings();
+
+ NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
+
+ mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
+ new UnderlyingNetworkInfo[0]);
+
+ // modify some number on wifi, and trigger poll event
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ // expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .insertEntry(TEST_IFACE, 1024L, 8L, 2048L, 16L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
+ .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
+
+ mService.noteUidForeground(UID_RED, false);
+ verify(mUidCounterSetMap, never()).deleteEntry(any());
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
+ mService.noteUidForeground(UID_RED, true);
+ verify(mUidCounterSetMap).updateEntry(
+ eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
+
+ forcePollAndWaitForIdle();
+ // Simulate shutdown to force persisting data
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+ assertStatsFilesExist(true);
+
+ // Move the files to the legacy directory to simulate an import from old data
+ for (File f : mStatsDir.listFiles()) {
+ Files.move(f.toPath(), mLegacyStatsDir.toPath().resolve(f.getName()));
+ }
+ assertStatsFilesExist(false);
+
+ // Fetch the stats from the legacy files and set platform stats collection to be identical
+ mPlatformNetworkStatsCollection.put(PREFIX_DEV,
+ getLegacyCollection(PREFIX_DEV, false /* includeTags */));
+ mPlatformNetworkStatsCollection.put(PREFIX_XT,
+ getLegacyCollection(PREFIX_XT, false /* includeTags */));
+ mPlatformNetworkStatsCollection.put(PREFIX_UID,
+ getLegacyCollection(PREFIX_UID, false /* includeTags */));
+ mPlatformNetworkStatsCollection.put(PREFIX_UID_TAG,
+ getLegacyCollection(PREFIX_UID_TAG, true /* includeTags */));
+
+ // Mock zero usage and boot through serviceReady(), verify there is no imported data.
+ expectDefaultSettings();
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+ mService.systemReady();
+ assertStatsFilesExist(false);
+
+ // Set the flag and reboot, verify the imported data is not there until next boot.
+ mStoreFilesInApexData = true;
+ mImportLegacyTargetAttempts = 3;
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+ assertStatsFilesExist(false);
+
+ // Boot through systemReady() again.
+ expectDefaultSettings();
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+ mService.systemReady();
+
+ // After systemReady(), the service should have historical stats loaded again.
+ // Thus, verify
+ // 1. The stats are absorbed by the recorder.
+ // 2. The imported data are persisted.
+ // 3. The attempts count is set to target attempts count to indicate a successful
+ // migration.
+ assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
+ assertStatsFilesExist(true);
+ verify(mImportLegacyAttemptsCounter).set(3);
+ verify(mImportLegacySuccessesCounter).set(1);
+
+ // TODO: Verify upgrading with Exception won't damege original data and
+ // will decrease the retry counter by 1.
+ }
+
+ private NetworkStatsRecorder makeTestRecorder(File directory, String prefix, Config config,
+ boolean includeTags) {
+ final NetworkStats.NonMonotonicObserver observer =
+ mock(NetworkStats.NonMonotonicObserver.class);
+ final DropBoxManager dropBox = mock(DropBoxManager.class);
+ return new NetworkStatsRecorder(new FileRotator(
+ directory, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
+ observer, dropBox, prefix, config.bucketDuration, includeTags);
+ }
+
+ private NetworkStatsCollection getLegacyCollection(String prefix, boolean includeTags) {
+ final NetworkStatsRecorder recorder = makeTestRecorder(mLegacyStatsDir, PREFIX_DEV,
+ mSettings.getDevConfig(), includeTags);
+ return recorder.getOrLoadCompleteLocked();
}
private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets,
@@ -1816,11 +1972,10 @@
}
private void assertStatsFilesExist(boolean exist) {
- final File basePath = new File(mStatsDir, "netstats");
if (exist) {
- assertTrue(basePath.list().length > 0);
+ assertTrue(mStatsDir.list().length > 0);
} else {
- assertTrue(basePath.list().length == 0);
+ assertTrue(mStatsDir.list().length == 0);
}
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
index 0d34609..622f2be 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
@@ -19,6 +19,8 @@
import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -37,7 +39,6 @@
import android.annotation.Nullable;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
-import android.os.Build;
import android.os.Looper;
import android.os.Parcel;
import android.telephony.SubscriptionManager;
@@ -63,7 +64,7 @@
import java.util.concurrent.Executors;
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public final class NetworkStatsSubscriptionsMonitorTest {
private static final int TEST_SUBID1 = 3;
private static final int TEST_SUBID2 = 5;
diff --git a/tests/unit/java/com/android/server/net/PersistentIntTest.kt b/tests/unit/java/com/android/server/net/PersistentIntTest.kt
new file mode 100644
index 0000000..9268352
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/PersistentIntTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net
+
+import android.util.SystemConfigFileCommitEventLogger
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
+import com.android.testutils.assertThrows
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+import java.io.IOException
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.attribute.PosixFilePermission
+import java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE
+import java.nio.file.attribute.PosixFilePermission.OWNER_READ
+import java.nio.file.attribute.PosixFilePermission.OWNER_WRITE
+import java.util.Random
+import kotlin.test.assertEquals
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(SC_V2)
+class PersistentIntTest {
+ val tempFilesCreated = mutableSetOf<Path>()
+ lateinit var tempDir: Path
+
+ @Before
+ fun setUp() {
+ tempDir = Files.createTempDirectory("tmp.PersistentIntTest.")
+ }
+
+ @After
+ fun tearDown() {
+ var permissions = setOf(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE)
+ Files.setPosixFilePermissions(tempDir, permissions)
+
+ for (file in tempFilesCreated) {
+ Files.deleteIfExists(file)
+ }
+ Files.delete(tempDir)
+ }
+
+ @Test
+ fun testNormalReadWrite() {
+ // New, initialized to 0.
+ val pi = createPersistentInt()
+ assertEquals(0, pi.get())
+ pi.set(12345)
+ assertEquals(12345, pi.get())
+
+ // Existing.
+ val pi2 = createPersistentInt(pathOf(pi))
+ assertEquals(12345, pi2.get())
+ }
+
+ @Test
+ fun testReadOrWriteFailsInCreate() {
+ setWritable(tempDir, false)
+ assertThrows(IOException::class.java) {
+ createPersistentInt()
+ }
+ }
+
+ @Test
+ fun testReadOrWriteFailsAfterCreate() {
+ val pi = createPersistentInt()
+ pi.set(42)
+ assertEquals(42, pi.get())
+
+ val path = pathOf(pi)
+ setReadable(path, false)
+ assertThrows(IOException::class.java) { pi.get() }
+ pi.set(77)
+
+ setReadable(path, true)
+ setWritable(path, false)
+ setWritable(tempDir, false) // Writing creates a new file+renames, make this fail.
+ assertThrows(IOException::class.java) { pi.set(99) }
+ assertEquals(77, pi.get())
+ }
+
+ fun addOrRemovePermission(p: Path, permission: PosixFilePermission, add: Boolean) {
+ val permissions = Files.getPosixFilePermissions(p)
+ if (add) {
+ permissions.add(permission)
+ } else {
+ permissions.remove(permission)
+ }
+ Files.setPosixFilePermissions(p, permissions)
+ }
+
+ fun setReadable(p: Path, readable: Boolean) {
+ addOrRemovePermission(p, OWNER_READ, readable)
+ }
+
+ fun setWritable(p: Path, writable: Boolean) {
+ addOrRemovePermission(p, OWNER_WRITE, writable)
+ }
+
+ fun pathOf(pi: PersistentInt): Path {
+ return File(pi.path).toPath()
+ }
+
+ fun createPersistentInt(path: Path = randomTempPath()): PersistentInt {
+ tempFilesCreated.add(path)
+ return PersistentInt(path.toString(),
+ SystemConfigFileCommitEventLogger("PersistentIntTest"))
+ }
+
+ fun randomTempPath(): Path {
+ return tempDir.resolve(Integer.toHexString(Random().nextInt())).also {
+ tempFilesCreated.add(it)
+ }
+ }
+}
diff --git a/tools/Android.bp b/tools/Android.bp
deleted file mode 100644
index 27f9b75..0000000
--- a/tools/Android.bp
+++ /dev/null
@@ -1,46 +0,0 @@
-//
-// 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"],
-}
-
-// Build tool used to generate jarjar rules for all classes in a jar, except those that are
-// API, UnsupportedAppUsage or otherwise excluded.
-python_binary_host {
- name: "jarjar-rules-generator",
- srcs: [
- "gen_jarjar.py",
- ],
- main: "gen_jarjar.py",
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- },
- },
- visibility: ["//packages/modules/Connectivity:__subpackages__"],
-}
-
-genrule_defaults {
- name: "jarjar-rules-combine-defaults",
- // Concat files with a line break in the middle
- cmd: "for src in $(in); do cat $${src}; echo; done > $(out)",
- defaults_visibility: ["//packages/modules/Connectivity:__subpackages__"],
-}
diff --git a/tools/gen_jarjar.py b/tools/gen_jarjar.py
deleted file mode 100755
index 285bf6f..0000000
--- a/tools/gen_jarjar.py
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# 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.
-
-""" This script generates jarjar rule files to add a jarjar prefix to all classes, except those
-that are API, unsupported API or otherwise excluded."""
-
-import argparse
-import io
-import re
-import subprocess
-from xml import sax
-from xml.sax.handler import ContentHandler
-from zipfile import ZipFile
-
-
-def parse_arguments(argv):
- parser = argparse.ArgumentParser()
- parser.add_argument(
- '--jars', nargs='+',
- help='Path to pre-jarjar JAR. Can be followed by multiple space-separated paths.')
- parser.add_argument(
- '--prefix', required=True,
- help='Package prefix to use for jarjared classes, '
- 'for example "com.android.connectivity" (does not end with a dot).')
- parser.add_argument(
- '--output', required=True, help='Path to output jarjar rules file.')
- parser.add_argument(
- '--apistubs', nargs='*', default=[],
- help='Path to API stubs jar. Classes that are API will not be jarjared. Can be followed by '
- 'multiple space-separated paths.')
- parser.add_argument(
- '--unsupportedapi', nargs='*', default=[],
- help='Path to UnsupportedAppUsage hidden API .txt lists. '
- 'Classes that have UnsupportedAppUsage API will not be jarjared. Can be followed by '
- 'multiple space-separated paths.')
- parser.add_argument(
- '--excludes', nargs='*', default=[],
- help='Path to files listing classes that should not be jarjared. Can be followed by '
- 'multiple space-separated paths. '
- 'Each file should contain one full-match regex per line. Empty lines or lines '
- 'starting with "#" are ignored.')
- parser.add_argument(
- '--dexdump', default='dexdump', help='Path to dexdump binary.')
- return parser.parse_args(argv)
-
-
-class DumpHandler(ContentHandler):
- def __init__(self):
- super().__init__()
- self._current_package = None
- self.classes = []
-
- def startElement(self, name, attrs):
- if name == 'package':
- attr_name = attrs.getValue('name')
- assert attr_name != '', '<package> element missing name'
- assert self._current_package is None, f'Found nested package tags for {attr_name}'
- self._current_package = attr_name
- elif name == 'class':
- attr_name = attrs.getValue('name')
- assert attr_name != '', '<class> element missing name'
- self.classes.append(self._current_package + '.' + attr_name)
-
- def endElement(self, name):
- if name == 'package':
- self._current_package = None
-
-
-def _list_toplevel_dex_classes(jar, dexdump):
- """List all classes in a dexed .jar file that are not inner classes."""
- # Empty jars do net get a classes.dex: return an empty set for them
- with ZipFile(jar, 'r') as zip_file:
- if not zip_file.namelist():
- return set()
- cmd = [dexdump, '-l', 'xml', '-e', jar]
- dump = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE)
- handler = DumpHandler()
- xml_parser = sax.make_parser()
- xml_parser.setContentHandler(handler)
- xml_parser.parse(io.StringIO(dump.stdout))
- return set([_get_toplevel_class(c) for c in handler.classes])
-
-
-def _list_jar_classes(jar):
- with ZipFile(jar, 'r') as zip:
- files = zip.namelist()
- assert 'classes.dex' not in files, f'Jar file {jar} is dexed, ' \
- 'expected an intermediate zip of .class files'
- class_len = len('.class')
- return [f.replace('/', '.')[:-class_len] for f in files
- if f.endswith('.class') and not f.endswith('/package-info.class')]
-
-
-def _list_hiddenapi_classes(txt_file):
- out = set()
- with open(txt_file, 'r') as f:
- for line in f:
- if not line.strip():
- continue
- assert line.startswith('L') and ';' in line, f'Class name not recognized: {line}'
- clazz = line.replace('/', '.').split(';')[0][1:]
- out.add(_get_toplevel_class(clazz))
- return out
-
-
-def _get_toplevel_class(clazz):
- """Return the name of the toplevel (not an inner class) enclosing class of the given class."""
- if '$' not in clazz:
- return clazz
- return clazz.split('$')[0]
-
-
-def _get_excludes(path):
- out = []
- with open(path, 'r') as f:
- for line in f:
- stripped = line.strip()
- if not stripped or stripped.startswith('#'):
- continue
- out.append(re.compile(stripped))
- return out
-
-
-def make_jarjar_rules(args):
- excluded_classes = set()
- for apistubs_file in args.apistubs:
- excluded_classes.update(_list_toplevel_dex_classes(apistubs_file, args.dexdump))
-
- for unsupportedapi_file in args.unsupportedapi:
- excluded_classes.update(_list_hiddenapi_classes(unsupportedapi_file))
-
- exclude_regexes = []
- for exclude_file in args.excludes:
- exclude_regexes.extend(_get_excludes(exclude_file))
-
- with open(args.output, 'w') as outfile:
- for jar in args.jars:
- jar_classes = _list_jar_classes(jar)
- jar_classes.sort()
- for clazz in jar_classes:
- if (_get_toplevel_class(clazz) not in excluded_classes and
- not any(r.fullmatch(clazz) for r in exclude_regexes)):
- outfile.write(f'rule {clazz} {args.prefix}.@0\n')
- # Also include jarjar rules for unit tests of the class, so the package matches
- outfile.write(f'rule {clazz}Test {args.prefix}.@0\n')
- outfile.write(f'rule {clazz}Test$* {args.prefix}.@0\n')
-
-
-def _main():
- # Pass in None to use argv
- args = parse_arguments(None)
- make_jarjar_rules(args)
-
-
-if __name__ == '__main__':
- _main()