Merge "cronet tests: Do not run integration tests on R"
diff --git a/Cronet/tests/cts/Android.bp b/Cronet/tests/cts/Android.bp
index ce8b2d9..a0b2434 100644
--- a/Cronet/tests/cts/Android.bp
+++ b/Cronet/tests/cts/Android.bp
@@ -54,7 +54,9 @@
defaults: [
"cts_defaults",
],
+ enforce_default_target_sdk_version: true,
sdk_version: "test_current",
+ min_sdk_version: "30",
static_libs: ["CtsNetHttpTestsLib"],
// Tag this as a cts test artifact
test_suites: [
diff --git a/Cronet/tests/mts/Android.bp b/Cronet/tests/mts/Android.bp
index 93564e4..4e4251c 100644
--- a/Cronet/tests/mts/Android.bp
+++ b/Cronet/tests/mts/Android.bp
@@ -34,25 +34,13 @@
"--output $(out)",
}
+// Library to be used in coverage tests. cronet_java_tests can't be used directly because the common
+// tests need to inherit the NetHttpTests manifest.
android_library {
name: "NetHttpTestsLibPreJarJar",
- srcs: [":cronet_aml_javatests_sources"],
+ static_libs: ["cronet_java_tests"],
sdk_version: "module_current",
min_sdk_version: "30",
- static_libs: [
- "cronet_testserver_utils",
- "androidx.test.ext.junit",
- "androidx.test.rules",
- "junit",
- "truth",
- ],
- libs: [
- "android.test.base",
- "framework-connectivity-pre-jarjar",
- // android.net.TrafficStats apis
- "framework-connectivity-t",
- ],
- lint: { test: true }
}
android_test {
diff --git a/Cronet/tests/mts/jarjar_excludes.txt b/Cronet/tests/mts/jarjar_excludes.txt
index cf3a017..a3e86de 100644
--- a/Cronet/tests/mts/jarjar_excludes.txt
+++ b/Cronet/tests/mts/jarjar_excludes.txt
@@ -5,6 +5,8 @@
# cronet_tests.so is not jarjared and uses base classes. We can remove this when there's a
# separate java base target to depend on.
org\.chromium\.base\..+
+J\.cronet_tests_N(\$.+)?
+
# Do not jarjar the tests and its utils as they also do JNI with cronet_tests.so
org\.chromium\.net\..*Test.*(\$.+)?
org\.chromium\.net\.NativeTestServer(\$.+)?
diff --git a/TEST_MAPPING b/TEST_MAPPING
index d2f6d6a..0326bf2 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -224,6 +224,14 @@
"exclude-annotation": "androidx.test.filters.LargeTest"
}
]
+ },
+ {
+ "name": "CtsTetheringTestLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
+ }
+ ]
}
],
"mainline-postsubmit": [
@@ -231,6 +239,15 @@
{
"name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"keywords": ["sim"]
+ },
+ {
+ "name": "CtsTetheringTestLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "keywords": ["sim"],
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
+ }
+ ]
}
],
"imports": [
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index b88ec7f..4478b1e 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -195,11 +195,7 @@
certificate: "networkstack",
manifest: "AndroidManifest.xml",
use_embedded_native_libs: true,
- // The network stack *must* be included to ensure security of the device
- required: [
- "NetworkStack",
- "privapp_allowlist_com.android.tethering",
- ],
+ privapp_allowlist: ":privapp_allowlist_com.android.tethering",
apex_available: ["com.android.tethering"],
lint: { strict_updatability_linting: true },
}
@@ -215,11 +211,7 @@
certificate: "networkstack",
manifest: "AndroidManifest.xml",
use_embedded_native_libs: true,
- // The network stack *must* be included to ensure security of the device
- required: [
- "NetworkStackNext",
- "privapp_allowlist_com.android.tethering",
- ],
+ privapp_allowlist: ":privapp_allowlist_com.android.tethering",
apex_available: ["com.android.tethering"],
lint: { strict_updatability_linting: true },
}
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 4bfd1fb..cf9b359 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -112,10 +112,7 @@
"ServiceConnectivityResources",
"HalfSheetUX",
],
- prebuilts: [
- "current_sdkinfo",
- "privapp_allowlist_com.android.tethering",
- ],
+ prebuilts: ["current_sdkinfo"],
manifest: "manifest.json",
key: "com.android.tethering.key",
// Indicates that pre-installed version of this apex can be compressed.
diff --git a/Tethering/apex/permissions/Android.bp b/Tethering/apex/permissions/Android.bp
index ac9ec65..69c1aa2 100644
--- a/Tethering/apex/permissions/Android.bp
+++ b/Tethering/apex/permissions/Android.bp
@@ -19,10 +19,7 @@
default_visibility: ["//packages/modules/Connectivity/Tethering:__subpackages__"],
}
-prebuilt_etc {
+filegroup {
name: "privapp_allowlist_com.android.tethering",
- sub_dir: "permissions",
- filename: "permissions.xml",
- src: "permissions.xml",
- installable: false,
+ srcs: ["permissions.xml"],
}
\ No newline at end of file
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 65ea8e5..6affb62 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -17,6 +17,8 @@
package android.net.ip;
import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
@@ -405,8 +407,8 @@
/** Internals. */
- private boolean startIPv4() {
- return configureIPv4(true);
+ private boolean startIPv4(int scope) {
+ return configureIPv4(true, scope);
}
/**
@@ -616,7 +618,7 @@
}
private void stopIPv4() {
- configureIPv4(false);
+ configureIPv4(false /* enabled */, CONNECTIVITY_SCOPE_GLOBAL /* not used */);
// NOTE: All of configureIPv4() will be refactored out of existence
// into calls to InterfaceController, shared with startIPv4().
mInterfaceCtrl.clearIPv4Address();
@@ -627,11 +629,11 @@
mStaticIpv4ClientAddr = null;
}
- private boolean configureIPv4(boolean enabled) {
+ private boolean configureIPv4(boolean enabled, int scope) {
if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")");
if (enabled) {
- mIpv4Address = requestIpv4Address(true /* useLastAddress */);
+ mIpv4Address = requestIpv4Address(scope, true /* useLastAddress */);
}
if (mIpv4Address == null) {
@@ -679,12 +681,12 @@
return (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) && !SdkLevel.isAtLeastT();
}
- private LinkAddress requestIpv4Address(final boolean useLastAddress) {
+ private LinkAddress requestIpv4Address(final int scope, final boolean useLastAddress) {
if (mStaticIpv4ServerAddr != null) return mStaticIpv4ServerAddr;
if (shouldNotConfigureBluetoothInterface()) return new LinkAddress(BLUETOOTH_IFACE_ADDR);
- return mPrivateAddressCoordinator.requestDownstreamAddress(this, useLastAddress);
+ return mPrivateAddressCoordinator.requestDownstreamAddress(this, scope, useLastAddress);
}
private boolean startIPv6() {
@@ -998,67 +1000,6 @@
}
}
- private void handleNewPrefixRequest(@NonNull final IpPrefix currentPrefix) {
- if (!currentPrefix.contains(mIpv4Address.getAddress())
- || currentPrefix.getPrefixLength() != mIpv4Address.getPrefixLength()) {
- Log.e(TAG, "Invalid prefix: " + currentPrefix);
- return;
- }
-
- final LinkAddress deprecatedLinkAddress = mIpv4Address;
- mIpv4Address = requestIpv4Address(false);
- if (mIpv4Address == null) {
- mLog.e("Fail to request a new downstream prefix");
- return;
- }
- final Inet4Address srvAddr = (Inet4Address) mIpv4Address.getAddress();
-
- // Add new IPv4 address on the interface.
- if (!mInterfaceCtrl.addAddress(srvAddr, currentPrefix.getPrefixLength())) {
- mLog.e("Failed to add new IP " + srvAddr);
- return;
- }
-
- // Remove deprecated routes from local network.
- removeRoutesFromLocalNetwork(
- Collections.singletonList(getDirectConnectedRoute(deprecatedLinkAddress)));
- mLinkProperties.removeLinkAddress(deprecatedLinkAddress);
-
- // Add new routes to local network.
- addRoutesToLocalNetwork(
- Collections.singletonList(getDirectConnectedRoute(mIpv4Address)));
- mLinkProperties.addLinkAddress(mIpv4Address);
-
- // Update local DNS caching server with new IPv4 address, otherwise, dnsmasq doesn't
- // listen on the interface configured with new IPv4 address, that results DNS validation
- // failure of downstream client even if appropriate routes have been configured.
- try {
- mNetd.tetherApplyDnsInterfaces();
- } catch (ServiceSpecificException | RemoteException e) {
- mLog.e("Failed to update local DNS caching server");
- return;
- }
- sendLinkProperties();
-
- // Notify DHCP server that new prefix/route has been applied on IpServer.
- final Inet4Address clientAddr = mStaticIpv4ClientAddr == null ? null :
- (Inet4Address) mStaticIpv4ClientAddr.getAddress();
- final DhcpServingParamsParcel params = makeServingParams(srvAddr /* defaultRouter */,
- srvAddr /* dnsServer */, mIpv4Address /* serverLinkAddress */, clientAddr);
- try {
- mDhcpServer.updateParams(params, new OnHandlerStatusCallback() {
- @Override
- public void callback(int statusCode) {
- if (statusCode != STATUS_SUCCESS) {
- mLog.e("Error updating DHCP serving params: " + statusCode);
- }
- }
- });
- } catch (RemoteException e) {
- mLog.e("Error updating DHCP serving params", e);
- }
- }
-
private byte getHopLimit(String upstreamIface, int adjustTTL) {
try {
int upstreamHopLimit = Integer.parseUnsignedInt(
@@ -1173,12 +1114,37 @@
mBpfCoordinator.stopMonitoring(this);
}
- class BaseServingState extends State {
+ abstract class BaseServingState extends State {
+ private final int mDesiredInterfaceState;
+
+ BaseServingState(int interfaceState) {
+ mDesiredInterfaceState = interfaceState;
+ }
+
@Override
public void enter() {
startConntrackMonitoring();
- if (!startIPv4()) {
+ startServingInterface();
+
+ if (mLastError != TETHER_ERROR_NO_ERROR) {
+ transitionTo(mInitialState);
+ }
+
+ if (DBG) Log.d(TAG, getStateString(mDesiredInterfaceState) + " serve " + mIfaceName);
+ sendInterfaceState(mDesiredInterfaceState);
+ }
+
+ private int getScope() {
+ if (mDesiredInterfaceState == STATE_TETHERED) {
+ return CONNECTIVITY_SCOPE_GLOBAL;
+ }
+
+ return CONNECTIVITY_SCOPE_LOCAL;
+ }
+
+ private void startServingInterface() {
+ if (!startIPv4(getScope())) {
mLastError = TETHER_ERROR_IFACE_CFG_ERROR;
return;
}
@@ -1257,6 +1223,67 @@
}
return true;
}
+
+ private void handleNewPrefixRequest(@NonNull final IpPrefix currentPrefix) {
+ if (!currentPrefix.contains(mIpv4Address.getAddress())
+ || currentPrefix.getPrefixLength() != mIpv4Address.getPrefixLength()) {
+ Log.e(TAG, "Invalid prefix: " + currentPrefix);
+ return;
+ }
+
+ final LinkAddress deprecatedLinkAddress = mIpv4Address;
+ mIpv4Address = requestIpv4Address(getScope(), false);
+ if (mIpv4Address == null) {
+ mLog.e("Fail to request a new downstream prefix");
+ return;
+ }
+ final Inet4Address srvAddr = (Inet4Address) mIpv4Address.getAddress();
+
+ // Add new IPv4 address on the interface.
+ if (!mInterfaceCtrl.addAddress(srvAddr, currentPrefix.getPrefixLength())) {
+ mLog.e("Failed to add new IP " + srvAddr);
+ return;
+ }
+
+ // Remove deprecated routes from local network.
+ removeRoutesFromLocalNetwork(
+ Collections.singletonList(getDirectConnectedRoute(deprecatedLinkAddress)));
+ mLinkProperties.removeLinkAddress(deprecatedLinkAddress);
+
+ // Add new routes to local network.
+ addRoutesToLocalNetwork(
+ Collections.singletonList(getDirectConnectedRoute(mIpv4Address)));
+ mLinkProperties.addLinkAddress(mIpv4Address);
+
+ // Update local DNS caching server with new IPv4 address, otherwise, dnsmasq doesn't
+ // listen on the interface configured with new IPv4 address, that results DNS validation
+ // failure of downstream client even if appropriate routes have been configured.
+ try {
+ mNetd.tetherApplyDnsInterfaces();
+ } catch (ServiceSpecificException | RemoteException e) {
+ mLog.e("Failed to update local DNS caching server");
+ return;
+ }
+ sendLinkProperties();
+
+ // Notify DHCP server that new prefix/route has been applied on IpServer.
+ final Inet4Address clientAddr = mStaticIpv4ClientAddr == null ? null :
+ (Inet4Address) mStaticIpv4ClientAddr.getAddress();
+ final DhcpServingParamsParcel params = makeServingParams(srvAddr /* defaultRouter */,
+ srvAddr /* dnsServer */, mIpv4Address /* serverLinkAddress */, clientAddr);
+ try {
+ mDhcpServer.updateParams(params, new OnHandlerStatusCallback() {
+ @Override
+ public void callback(int statusCode) {
+ if (statusCode != STATUS_SUCCESS) {
+ mLog.e("Error updating DHCP serving params: " + statusCode);
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ mLog.e("Error updating DHCP serving params", e);
+ }
+ }
}
// Handling errors in BaseServingState.enter() by transitioning is
@@ -1265,15 +1292,8 @@
// and forwarding and NAT rules should be handled by a coordinating
// functional element outside of IpServer.
class LocalHotspotState extends BaseServingState {
- @Override
- public void enter() {
- super.enter();
- if (mLastError != TETHER_ERROR_NO_ERROR) {
- transitionTo(mInitialState);
- }
-
- if (DBG) Log.d(TAG, "Local hotspot " + mIfaceName);
- sendInterfaceState(STATE_LOCAL_ONLY);
+ LocalHotspotState() {
+ super(STATE_LOCAL_ONLY);
}
@Override
@@ -1301,15 +1321,8 @@
// and forwarding and NAT rules should be handled by a coordinating
// functional element outside of IpServer.
class TetheredState extends BaseServingState {
- @Override
- public void enter() {
- super.enter();
- if (mLastError != TETHER_ERROR_NO_ERROR) {
- transitionTo(mInitialState);
- }
-
- if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
- sendInterfaceState(STATE_TETHERED);
+ TetheredState() {
+ super(STATE_TETHERED);
}
@Override
diff --git a/Tethering/src/com/android/networkstack/tethering/ConnectedClientsTracker.java b/Tethering/src/com/android/networkstack/tethering/ConnectedClientsTracker.java
index 8a96988..5b19c4e 100644
--- a/Tethering/src/com/android/networkstack/tethering/ConnectedClientsTracker.java
+++ b/Tethering/src/com/android/networkstack/tethering/ConnectedClientsTracker.java
@@ -29,6 +29,8 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -49,6 +51,8 @@
@NonNull
private List<WifiClient> mLastWifiClients = Collections.emptyList();
@NonNull
+ private List<WifiClient> mLastLocalOnlyClients = Collections.emptyList();
+ @NonNull
private List<TetheredClient> mLastTetheredClients = Collections.emptyList();
@VisibleForTesting
@@ -72,25 +76,44 @@
*
* <p>The new list can be obtained through {@link #getLastTetheredClients()}.
* @param ipServers The IpServers used to assign addresses to clients.
- * @param wifiClients The list of L2-connected WiFi clients. Null for no change since last
- * update.
+ * @param wifiClients The list of L2-connected WiFi clients that are connected to a global
+ * hotspot. Null for no change since last update.
+ * @param localOnlyClients The list of L2-connected WiFi clients that are connected to localOnly
+ * hotspot. Null for no change since last update.
* @return True if the list of clients changed since the last calculation.
*/
public boolean updateConnectedClients(
- Iterable<IpServer> ipServers, @Nullable List<WifiClient> wifiClients) {
+ Iterable<IpServer> ipServers, @Nullable List<WifiClient> wifiClients,
+ @Nullable List<WifiClient> localOnlyClients) {
final long now = mClock.elapsedRealtime();
- if (wifiClients != null) {
- mLastWifiClients = wifiClients;
- }
+ if (wifiClients != null) mLastWifiClients = wifiClients;
+ if (localOnlyClients != null) mLastLocalOnlyClients = localOnlyClients;
+
final Set<MacAddress> wifiClientMacs = getClientMacs(mLastWifiClients);
+ final Set<MacAddress> localOnlyClientMacs = getClientMacs(mLastLocalOnlyClients);
// Build the list of non-expired leases from all IpServers, grouped by mac address
final Map<MacAddress, TetheredClient> clientsMap = new HashMap<>();
for (IpServer server : ipServers) {
+ final Set<MacAddress> connectedClientMacs;
+ switch (server.servingMode()) {
+ case IpServer.STATE_TETHERED:
+ connectedClientMacs = wifiClientMacs;
+ break;
+ case IpServer.STATE_LOCAL_ONLY:
+ // Before T, SAP and LOHS both use wifiClientMacs because
+ // registerLocalOnlyHotspotSoftApCallback didn't exist.
+ connectedClientMacs = SdkLevel.isAtLeastT()
+ ? localOnlyClientMacs : wifiClientMacs;
+ break;
+ default:
+ continue;
+ }
+
for (TetheredClient client : server.getAllLeases()) {
if (client.getTetheringType() == TETHERING_WIFI
- && !wifiClientMacs.contains(client.getMacAddress())) {
+ && !connectedClientMacs.contains(client.getMacAddress())) {
// Skip leases of WiFi clients that are not (or no longer) L2-connected
continue;
}
@@ -104,11 +127,8 @@
// TODO: add IPv6 addresses from netlink
// Add connected WiFi clients that do not have any known address
- for (MacAddress client : wifiClientMacs) {
- if (clientsMap.containsKey(client)) continue;
- clientsMap.put(client, new TetheredClient(
- client, Collections.emptyList() /* addresses */, TETHERING_WIFI));
- }
+ addWifiClientsIfNoLeases(clientsMap, wifiClientMacs);
+ addWifiClientsIfNoLeases(clientsMap, localOnlyClientMacs);
final HashSet<TetheredClient> clients = new HashSet<>(clientsMap.values());
final boolean clientsChanged = clients.size() != mLastTetheredClients.size()
@@ -117,6 +137,15 @@
return clientsChanged;
}
+ private static void addWifiClientsIfNoLeases(
+ final Map<MacAddress, TetheredClient> clientsMap, final Set<MacAddress> clientMacs) {
+ for (MacAddress mac : clientMacs) {
+ if (clientsMap.containsKey(mac)) continue;
+ clientsMap.put(mac, new TetheredClient(
+ mac, Collections.emptyList() /* addresses */, TETHERING_WIFI));
+ }
+ }
+
private static void addLease(Map<MacAddress, TetheredClient> clientsMap, TetheredClient lease) {
final TetheredClient aggregateClient = clientsMap.getOrDefault(
lease.getMacAddress(), lease);
diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index 41a10ae..6c0ca82 100644
--- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -16,6 +16,8 @@
package com.android.networkstack.tethering;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
@@ -34,7 +36,6 @@
import android.net.ip.IpServer;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -77,7 +78,7 @@
private final ConnectivityManager mConnectivityMgr;
private final TetheringConfiguration mConfig;
// keyed by downstream type(TetheringManager.TETHERING_*).
- private final SparseArray<LinkAddress> mCachedAddresses;
+ private final ArrayMap<AddressKey, LinkAddress> mCachedAddresses;
public PrivateAddressCoordinator(Context context, TetheringConfiguration config) {
mDownstreams = new ArraySet<>();
@@ -85,10 +86,12 @@
mConnectivityMgr = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
mConfig = config;
- mCachedAddresses = new SparseArray<>();
+ mCachedAddresses = new ArrayMap<AddressKey, LinkAddress>();
// Reserved static addresses for bluetooth and wifi p2p.
- mCachedAddresses.put(TETHERING_BLUETOOTH, new LinkAddress(LEGACY_BLUETOOTH_IFACE_ADDRESS));
- mCachedAddresses.put(TETHERING_WIFI_P2P, new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS));
+ mCachedAddresses.put(new AddressKey(TETHERING_BLUETOOTH, CONNECTIVITY_SCOPE_GLOBAL),
+ new LinkAddress(LEGACY_BLUETOOTH_IFACE_ADDRESS));
+ mCachedAddresses.put(new AddressKey(TETHERING_WIFI_P2P, CONNECTIVITY_SCOPE_LOCAL),
+ new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS));
mTetheringPrefixes = new ArrayList<>(Arrays.asList(new IpPrefix("192.168.0.0/16"),
new IpPrefix("172.16.0.0/12"), new IpPrefix("10.0.0.0/8")));
@@ -166,16 +169,18 @@
* returns null if there is no available address.
*/
@Nullable
- public LinkAddress requestDownstreamAddress(final IpServer ipServer, boolean useLastAddress) {
+ public LinkAddress requestDownstreamAddress(final IpServer ipServer, final int scope,
+ boolean useLastAddress) {
if (mConfig.shouldEnableWifiP2pDedicatedIp()
&& ipServer.interfaceType() == TETHERING_WIFI_P2P) {
return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS);
}
+ final AddressKey addrKey = new AddressKey(ipServer.interfaceType(), scope);
// 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());
+ final LinkAddress cachedAddress = mCachedAddresses.get(addrKey);
if (useLastAddress && cachedAddress != null
&& !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
mDownstreams.add(ipServer);
@@ -186,7 +191,7 @@
final LinkAddress newAddress = chooseDownstreamAddress(prefixRange);
if (newAddress != null) {
mDownstreams.add(ipServer);
- mCachedAddresses.put(ipServer.interfaceType(), newAddress);
+ mCachedAddresses.put(addrKey, newAddress);
return newAddress;
}
}
@@ -384,6 +389,34 @@
return asIpPrefix(address);
}
+ private static class AddressKey {
+ private final int mTetheringType;
+ private final int mScope;
+
+ private AddressKey(int type, int scope) {
+ mTetheringType = type;
+ mScope = scope;
+ }
+
+ @Override
+ public int hashCode() {
+ return (mTetheringType << 16) + mScope;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof AddressKey)) return false;
+ final AddressKey other = (AddressKey) obj;
+
+ return mTetheringType == other.mTetheringType && mScope == other.mScope;
+ }
+
+ @Override
+ public String toString() {
+ return "AddressKey(" + mTetheringType + ", " + mScope + ")";
+ }
+ }
+
void dump(final IndentingPrintWriter pw) {
pw.println("mTetheringPrefixes:");
pw.increaseIndent();
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 4c5bf4e..e5f644e 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -58,6 +58,7 @@
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.IFACE_IP_MODE_UNSPECIFIED;
+import static android.net.wifi.WifiManager.SoftApCallback;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -479,15 +480,15 @@
mStateReceiver, noUpstreamFilter, PERMISSION_MAINLINE_NETWORK_STACK, mHandler);
final WifiManager wifiManager = getWifiManager();
- TetheringSoftApCallback softApCallback = new TetheringSoftApCallback();
if (wifiManager != null) {
- wifiManager.registerSoftApCallback(mExecutor, softApCallback);
- }
- if (SdkLevel.isAtLeastT() && wifiManager != null) {
- // 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);
+ wifiManager.registerSoftApCallback(mExecutor, new TetheringSoftApCallback());
+ if (SdkLevel.isAtLeastT()) {
+ // Although WifiManager#registerLocalOnlyHotspotSoftApCallback document that it need
+ // NEARBY_WIFI_DEVICES permission, but actually a caller who have NETWORK_STACK
+ // or MAINLINE_NETWORK_STACK permission can also use this API.
+ wifiManager.registerLocalOnlyHotspotSoftApCallback(mExecutor,
+ new LocalOnlyHotspotCallback());
+ }
}
startTrackDefaultNetwork();
@@ -573,26 +574,17 @@
}
}
- private class TetheringSoftApCallback implements WifiManager.SoftApCallback {
- // TODO: Remove onStateChanged override when this method has default on
- // WifiManager#SoftApCallback interface.
- // Wifi listener for state change of the soft AP
- @Override
- public void onStateChanged(final int state, final int failureReason) {
- // Nothing
- }
-
- // 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.
+ private class TetheringSoftApCallback implements SoftApCallback {
@Override
public void onConnectedClientsChanged(final List<WifiClient> clients) {
- updateConnectedClients(clients);
+ updateConnectedClients(clients, null);
+ }
+ }
+
+ private class LocalOnlyHotspotCallback implements SoftApCallback {
+ @Override
+ public void onConnectedClientsChanged(final List<WifiClient> clients) {
+ updateConnectedClients(null, clients);
}
}
@@ -1968,7 +1960,7 @@
mIPv6TetheringCoordinator.removeActiveDownstream(who);
mOffload.excludeDownstreamInterface(who.interfaceName());
mForwardedDownstreams.remove(who);
- updateConnectedClients(null /* wifiClients */);
+ maybeDhcpLeasesChanged();
// If this is a Wi-Fi interface, tell WifiManager of any errors
// or the inactive serving state.
@@ -2710,9 +2702,15 @@
if (e != null) throw e;
}
- private void updateConnectedClients(final List<WifiClient> wifiClients) {
+ private void maybeDhcpLeasesChanged() {
+ // null means wifi clients did not change.
+ updateConnectedClients(null, null);
+ }
+
+ private void updateConnectedClients(final List<WifiClient> wifiClients,
+ final List<WifiClient> localOnlyClients) {
if (mConnectedClientsTracker.updateConnectedClients(mTetherMainSM.getAllDownstreams(),
- wifiClients)) {
+ wifiClients, localOnlyClients)) {
reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
}
}
@@ -2731,7 +2729,7 @@
@Override
public void dhcpLeasesChanged() {
- updateConnectedClients(null /* wifiClients */);
+ maybeDhcpLeasesChanged();
}
@Override
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 4ee5c42..5d57aa5 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -65,6 +65,7 @@
import com.android.net.module.util.structs.UdpHeader;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.NetworkStackModuleTest;
import com.android.testutils.TapPacketReader;
import org.junit.Rule;
@@ -860,6 +861,8 @@
(byte) 0x00, (byte) 0x08, (byte) 0x3a, (byte) 0xdf
};
+ // This test requires the update in NetworkStackModule(See b/269692093).
+ @NetworkStackModuleTest
@Test
public void testTetherZeroLengthDhcpPacket() throws Exception {
final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index f0d9057..46e50ef 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -19,6 +19,8 @@
import static android.net.INetd.IF_STATE_DOWN;
import static android.net.INetd.IF_STATE_UP;
import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_NCM;
import static android.net.TetheringManager.TETHERING_USB;
@@ -48,6 +50,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
@@ -271,8 +274,8 @@
dispatchTetherConnectionChanged(upstreamIface, lp, 0);
}
reset(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator);
- when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
- mTestAddress);
+ when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
+ anyBoolean())).thenReturn(mTestAddress);
}
@SuppressWarnings("DoNotCall") // Ignore warning for synchronous to call to Thread.run()
@@ -293,8 +296,8 @@
@Before public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
- when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
- mTestAddress);
+ when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
+ anyBoolean())).thenReturn(mTestAddress);
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(DEFAULT_USING_BPF_OFFLOAD);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(false /* default value */);
@@ -428,7 +431,8 @@
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
if (isAtLeastT()) {
- inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
+ inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
+ eq(CONNECTIVITY_SCOPE_GLOBAL), eq(true));
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
}
@@ -477,7 +481,8 @@
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
- inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
+ inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
+ eq(CONNECTIVITY_SCOPE_GLOBAL), eq(true));
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -498,7 +503,8 @@
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
- inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
+ inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
+ eq(CONNECTIVITY_SCOPE_LOCAL), eq(true));
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -766,7 +772,8 @@
final ArgumentCaptor<LinkProperties> lpCaptor =
ArgumentCaptor.forClass(LinkProperties.class);
InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator);
- inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
+ inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
+ eq(CONNECTIVITY_SCOPE_LOCAL), eq(true));
inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
// One for ipv4 route, one for ipv6 link local route.
inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
@@ -779,12 +786,13 @@
// Simulate the DHCP server receives DHCPDECLINE on MirrorLink and then signals
// onNewPrefixRequest callback.
final LinkAddress newAddress = new LinkAddress("192.168.100.125/24");
- when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
- newAddress);
+ when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
+ anyBoolean())).thenReturn(newAddress);
eventCallbacks.onNewPrefixRequest(new IpPrefix("192.168.42.0/24"));
mLooper.dispatchAll();
- inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(false));
+ inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
+ eq(CONNECTIVITY_SCOPE_LOCAL), eq(false));
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
verifyNoMoreInteractions(mCallback);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/ConnectedClientsTrackerTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/ConnectedClientsTrackerTest.kt
index d915354..2dd9f91 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/ConnectedClientsTrackerTest.kt
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/ConnectedClientsTrackerTest.kt
@@ -24,19 +24,25 @@
import android.net.TetheringManager.TETHERING_WIFI
import android.net.ip.IpServer
import android.net.wifi.WifiClient
+import android.os.Build
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
@SmallTest
class ConnectedClientsTrackerTest {
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
private val server1 = mock(IpServer::class.java)
private val server2 = mock(IpServer::class.java)
@@ -70,55 +76,122 @@
@Test
fun testUpdateConnectedClients() {
+ doReturn(IpServer.STATE_TETHERED).`when`(server1).servingMode()
+ doReturn(IpServer.STATE_TETHERED).`when`(server2).servingMode()
+ runUpdateConnectedClientsTest(isGlobal = true)
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ fun testUpdateConnectedClients_LocalOnly() {
+ doReturn(IpServer.STATE_LOCAL_ONLY).`when`(server1).servingMode()
+ doReturn(IpServer.STATE_LOCAL_ONLY).`when`(server2).servingMode()
+ runUpdateConnectedClientsTest(isGlobal = false)
+ }
+
+ fun runUpdateConnectedClientsTest(isGlobal: Boolean) {
doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
doReturn(emptyList<TetheredClient>()).`when`(server2).allLeases
val tracker = ConnectedClientsTracker(clock)
- assertFalse(tracker.updateConnectedClients(servers, null))
+ assertFalse(tracker.updateConnectedClients(servers, null, null))
// Obtain a lease for client 1
doReturn(listOf(client1)).`when`(server1).allLeases
- assertSameClients(listOf(client1), assertNewClients(tracker, servers, listOf(wifiClient1)))
+ if (isGlobal) {
+ assertSameClients(listOf(client1), assertNewClients(tracker, servers,
+ wifiClients = listOf(wifiClient1)))
+ } else {
+ assertSameClients(listOf(client1), assertNewClients(tracker, servers,
+ localOnlyClients = listOf(wifiClient1)))
+ }
// Client 2 L2-connected, no lease yet
val client2WithoutAddr = TetheredClient(client2Addr, emptyList(), TETHERING_WIFI)
- assertSameClients(listOf(client1, client2WithoutAddr),
- assertNewClients(tracker, servers, listOf(wifiClient1, wifiClient2)))
+ if (isGlobal) {
+ assertSameClients(listOf(client1, client2WithoutAddr), assertNewClients(
+ tracker, servers, wifiClients = listOf(wifiClient1, wifiClient2)))
+ } else {
+ assertSameClients(listOf(client1, client2WithoutAddr), assertNewClients(
+ tracker, servers, localOnlyClients = listOf(wifiClient1, wifiClient2)))
+ }
// Client 2 lease obtained
doReturn(listOf(client1, client2)).`when`(server1).allLeases
- assertSameClients(listOf(client1, client2), assertNewClients(tracker, servers, null))
+ assertSameClients(listOf(client1, client2), assertNewClients(tracker, servers))
// Client 3 lease obtained
doReturn(listOf(client3)).`when`(server2).allLeases
- assertSameClients(listOf(client1, client2, client3),
- assertNewClients(tracker, servers, null))
+ assertSameClients(listOf(client1, client2, client3), assertNewClients(tracker, servers))
- // Client 2 L2-disconnected
- assertSameClients(listOf(client1, client3),
- assertNewClients(tracker, servers, listOf(wifiClient1)))
-
- // Client 1 L2-disconnected
- assertSameClients(listOf(client3), assertNewClients(tracker, servers, emptyList()))
-
- // Client 1 comes back
- assertSameClients(listOf(client1, client3),
- assertNewClients(tracker, servers, listOf(wifiClient1)))
+ if (isGlobal) {
+ // Client 2 L2-disconnected
+ assertSameClients(listOf(client1, client3),
+ assertNewClients(tracker, servers, wifiClients = listOf(wifiClient1)))
+ // Client 1 L2-disconnected
+ assertSameClients(listOf(client3), assertNewClients(tracker, servers,
+ wifiClients = emptyList()))
+ // Client 1 comes back
+ assertSameClients(listOf(client1, client3),
+ assertNewClients(tracker, servers, wifiClients = listOf(wifiClient1)))
+ } else {
+ // Client 2 L2-disconnected
+ assertSameClients(listOf(client1, client3),
+ assertNewClients(tracker, servers, localOnlyClients = listOf(wifiClient1)))
+ // Client 1 L2-disconnected
+ assertSameClients(listOf(client3),
+ assertNewClients(tracker, servers, localOnlyClients = emptyList()))
+ // Client 1 comes back
+ assertSameClients(listOf(client1, client3),
+ assertNewClients(tracker, servers, localOnlyClients = listOf(wifiClient1)))
+ }
// Leases lost, client 1 still L2-connected
doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
doReturn(emptyList<TetheredClient>()).`when`(server2).allLeases
assertSameClients(listOf(TetheredClient(client1Addr, emptyList(), TETHERING_WIFI)),
- assertNewClients(tracker, servers, null))
+ assertNewClients(tracker, servers))
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ fun testLocalOnlyAndTetheredHotspotClients() {
+ val tracker = ConnectedClientsTracker(clock)
+ doReturn(IpServer.STATE_LOCAL_ONLY).`when`(server1).servingMode()
+ doReturn(IpServer.STATE_TETHERED).`when`(server2).servingMode()
+
+ // Client 1 connected to server1 (LOHS)
+ doReturn(listOf(client1)).`when`(server1).allLeases
+ doReturn(emptyList<TetheredClient>()).`when`(server2).allLeases
+ assertSameClients(listOf(client1), assertNewClients(tracker, servers,
+ localOnlyClients = listOf(wifiClient1)))
+
+ // Client 2 connected to server2 (wifi Tethering)
+ doReturn(listOf(client2)).`when`(server2).allLeases
+ assertSameClients(listOf(client1, client2), assertNewClients(tracker, servers,
+ listOf(wifiClient2), listOf(wifiClient1)))
+
+ // Client 2 L2-disconnected but lease doesn't expired yet
+ assertSameClients(listOf(client1), assertNewClients(tracker, servers,
+ wifiClients = emptyList()))
+
+ // Client 1 lease lost but still L2-connected
+ doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
+ val client1WithoutAddr = TetheredClient(client1Addr, emptyList(), TETHERING_WIFI)
+ assertSameClients(listOf(client1WithoutAddr), assertNewClients(tracker, servers))
+
+ // Client 1 L2-disconnected
+ assertSameClients(emptyList(), assertNewClients(tracker, servers,
+ localOnlyClients = emptyList()))
}
@Test
fun testUpdateConnectedClients_LeaseExpiration() {
+ doReturn(IpServer.STATE_TETHERED).`when`(server1).servingMode()
+ doReturn(IpServer.STATE_TETHERED).`when`(server2).servingMode()
val tracker = ConnectedClientsTracker(clock)
doReturn(listOf(client1, client2)).`when`(server1).allLeases
doReturn(listOf(client3)).`when`(server2).allLeases
assertSameClients(listOf(client1, client2, client3), assertNewClients(
- tracker, servers, listOf(wifiClient1, wifiClient2)))
+ tracker, servers, wifiClients = listOf(wifiClient1, wifiClient2)))
clock.time += 20
// Client 3 has no remaining lease: removed
@@ -131,15 +204,16 @@
// Only the "t + 30" address is left, the "t + 10" address expired
listOf(client2Exp30AddrInfo),
TETHERING_WIFI))
- assertSameClients(expectedClients, assertNewClients(tracker, servers, null))
+ assertSameClients(expectedClients, assertNewClients(tracker, servers))
}
private fun assertNewClients(
tracker: ConnectedClientsTracker,
ipServers: Iterable<IpServer>,
- wifiClients: List<WifiClient>?
+ wifiClients: List<WifiClient>? = null,
+ localOnlyClients: List<WifiClient>? = null
): List<TetheredClient> {
- assertTrue(tracker.updateConnectedClients(ipServers, wifiClients))
+ assertTrue(tracker.updateConnectedClients(ipServers, wifiClients, localOnlyClients))
return tracker.lastTetheredClients
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index 55d9852..91b092a 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -19,6 +19,8 @@
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
@@ -61,6 +63,7 @@
private static final String TEST_IFNAME = "test0";
@Mock private IpServer mHotspotIpServer;
+ @Mock private IpServer mLocalHotspotIpServer;
@Mock private IpServer mUsbIpServer;
@Mock private IpServer mEthernetIpServer;
@Mock private IpServer mWifiP2pIpServer;
@@ -90,6 +93,7 @@
when(mUsbIpServer.interfaceType()).thenReturn(TETHERING_USB);
when(mEthernetIpServer.interfaceType()).thenReturn(TETHERING_ETHERNET);
when(mHotspotIpServer.interfaceType()).thenReturn(TETHERING_WIFI);
+ when(mLocalHotspotIpServer.interfaceType()).thenReturn(TETHERING_WIFI);
when(mWifiP2pIpServer.interfaceType()).thenReturn(TETHERING_WIFI_P2P);
}
@@ -104,9 +108,10 @@
mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig));
}
- private LinkAddress requestDownstreamAddress(final IpServer ipServer, boolean useLastAddress) {
+ private LinkAddress requestDownstreamAddress(final IpServer ipServer, int scope,
+ boolean useLastAddress) {
final LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
- ipServer, useLastAddress);
+ ipServer, scope, useLastAddress);
when(ipServer.getAddress()).thenReturn(address);
return address;
}
@@ -115,19 +120,19 @@
public void testRequestDownstreamAddressWithoutUsingLastAddress() throws Exception {
final IpPrefix bluetoothPrefix = asIpPrefix(mBluetoothAddress);
final LinkAddress address = requestDownstreamAddress(mHotspotIpServer,
- false /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
final IpPrefix hotspotPrefix = asIpPrefix(address);
assertNotEquals(hotspotPrefix, bluetoothPrefix);
final LinkAddress newAddress = requestDownstreamAddress(mHotspotIpServer,
- false /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
final IpPrefix testDupRequest = asIpPrefix(newAddress);
assertNotEquals(hotspotPrefix, testDupRequest);
assertNotEquals(bluetoothPrefix, testDupRequest);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
- false /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
final IpPrefix usbPrefix = asIpPrefix(usbAddress);
assertNotEquals(usbPrefix, bluetoothPrefix);
assertNotEquals(usbPrefix, hotspotPrefix);
@@ -139,25 +144,28 @@
int fakeSubAddr = 0x2b00; // 43.0.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
LinkAddress actualAddress = requestDownstreamAddress(mHotspotIpServer,
- false /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
assertEquals(new LinkAddress("192.168.43.2/24"), actualAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
fakeSubAddr = 0x2d01; // 45.1.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
- actualAddress = requestDownstreamAddress(mHotspotIpServer, false /* useLastAddress */);
+ actualAddress = requestDownstreamAddress(mHotspotIpServer,
+ CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
assertEquals(new LinkAddress("192.168.45.2/24"), actualAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
fakeSubAddr = 0x2eff; // 46.255.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
- actualAddress = requestDownstreamAddress(mHotspotIpServer, false /* useLastAddress */);
+ actualAddress = requestDownstreamAddress(mHotspotIpServer,
+ CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
assertEquals(new LinkAddress("192.168.46.254/24"), actualAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
fakeSubAddr = 0x2f05; // 47.5.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
- actualAddress = requestDownstreamAddress(mHotspotIpServer, false /* useLastAddress */);
+ actualAddress = requestDownstreamAddress(mHotspotIpServer,
+ CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
assertEquals(new LinkAddress("192.168.47.5/24"), actualAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
}
@@ -168,7 +176,7 @@
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
getSubAddress(mBluetoothAddress.getAddress().getAddress()));
final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
- false /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddress);
assertNotEquals(asIpPrefix(mBluetoothAddress), hotspotPrefix);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
@@ -177,7 +185,7 @@
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
getSubAddress(hotspotAddress.getAddress().getAddress()));
final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
- false /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
final IpPrefix usbPrefix = asIpPrefix(usbAddress);
assertNotEquals(asIpPrefix(mBluetoothAddress), usbPrefix);
assertNotEquals(hotspotPrefix, usbPrefix);
@@ -187,7 +195,7 @@
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress()));
final LinkAddress etherAddress = requestDownstreamAddress(mEthernetIpServer,
- false /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
final IpPrefix etherPrefix = asIpPrefix(etherAddress);
assertNotEquals(asIpPrefix(mLegacyWifiP2pAddress), etherPrefix);
assertNotEquals(asIpPrefix(mBluetoothAddress), etherPrefix);
@@ -200,11 +208,11 @@
final int fakeHotspotSubAddr = 0x2b05; // 43.5
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.43.5/24"), hotspotAddress);
final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.45.5/24"), usbAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
@@ -214,10 +222,10 @@
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
final LinkAddress newHotspotAddress = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals(hotspotAddress, newHotspotAddress);
final LinkAddress newUsbAddress = requestDownstreamAddress(mUsbIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals(usbAddress, newUsbAddress);
final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
@@ -257,7 +265,7 @@
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
// - Enable hotspot with prefix 192.168.43.0/24
final LinkAddress hotspotAddr = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddr);
assertEquals("Wrong wifi prefix: ", predefinedPrefix, hotspotPrefix);
// - test mobile network with null NetworkCapabilities. Ideally this should not happen
@@ -311,21 +319,21 @@
// - Restart hotspot again and its prefix is different previous.
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
final LinkAddress hotspotAddr2 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
final IpPrefix hotspotPrefix2 = asIpPrefix(hotspotAddr2);
assertNotEquals(hotspotPrefix, hotspotPrefix2);
mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyWifi);
verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
// - Usb tethering can be enabled and its prefix is different with conflict one.
final LinkAddress usbAddr = requestDownstreamAddress(mUsbIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
final IpPrefix usbPrefix = asIpPrefix(usbAddr);
assertNotEquals(predefinedPrefix, usbPrefix);
assertNotEquals(hotspotPrefix2, usbPrefix);
// - Disable wifi upstream, then wifi's prefix can be selected again.
mPrivateAddressCoordinator.removeUpstreamPrefix(mWifiNetwork);
final LinkAddress ethAddr = requestDownstreamAddress(mEthernetIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
final IpPrefix ethPrefix = asIpPrefix(ethAddr);
assertEquals(predefinedPrefix, ethPrefix);
}
@@ -335,7 +343,7 @@
final int randomAddress = 0x8605; // 134.5
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
final LinkAddress addr0 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
// Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.134.5.
assertEquals("Wrong prefix: ", new LinkAddress("192.168.134.5/24"), addr0);
final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
@@ -345,7 +353,7 @@
// Check whether return address is next prefix of 192.168.134.0/24.
final LinkAddress addr1 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.135.5/24"), addr1);
final UpstreamNetworkState wifiUpstream2 = buildUpstreamNetworkState(mWifiNetwork,
new LinkAddress("192.168.149.16/19"), null,
@@ -355,7 +363,7 @@
// The conflict range is 128 ~ 159, so the address is 192.168.160.5/24.
final LinkAddress addr2 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.160.5/24"), addr2);
final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork,
new LinkAddress("192.168.129.53/18"), null,
@@ -370,7 +378,7 @@
// The conflict range are 128 ~ 159 and 159 ~ 191, so the address is 192.168.192.5/24.
final LinkAddress addr3 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.192.5/24"), addr3);
final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
new LinkAddress("192.168.188.133/17"), null,
@@ -380,7 +388,7 @@
// Conflict range: 128 ~ 255. The next available address is 192.168.0.5 because
// 192.168.134/24 ~ 192.168.255.255/24 is not available.
final LinkAddress addr4 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.0.5/24"), addr4);
final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
new LinkAddress("192.168.3.59/21"), null,
@@ -389,7 +397,7 @@
// Conflict ranges: 128 ~ 255 and 0 ~ 7, so the address is 192.168.8.5/24.
final LinkAddress addr5 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr5);
final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
new LinkAddress("192.168.68.43/21"), null,
@@ -399,7 +407,7 @@
// Update an upstream that does *not* conflict, check whether return the same address
// 192.168.5/24.
final LinkAddress addr6 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr6);
final UpstreamNetworkState mobileUpstream6 = buildUpstreamNetworkState(mMobileNetwork6,
new LinkAddress("192.168.10.97/21"), null,
@@ -408,7 +416,7 @@
// Conflict ranges: 0 ~ 15 and 128 ~ 255, so the address is 192.168.16.5/24.
final LinkAddress addr7 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.16.5/24"), addr7);
final UpstreamNetworkState mobileUpstream7 = buildUpstreamNetworkState(mMobileNetwork6,
new LinkAddress("192.168.0.0/17"), null,
@@ -417,7 +425,7 @@
// Choose prefix from next range(172.16.0.0/12) when no available prefix in 192.168.0.0/16.
final LinkAddress addr8 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.16.134.5/24"), addr8);
}
@@ -426,7 +434,7 @@
final int randomAddress = 0x1f2b2a; // 31.43.42
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
final LinkAddress classC1 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
// Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.43.42.
assertEquals("Wrong prefix: ", new LinkAddress("192.168.43.42/24"), classC1);
final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
@@ -437,7 +445,7 @@
// Check whether return address is next address of prefix 192.168.128.0/17.
final LinkAddress classC2 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.128.42/24"), classC2);
final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork,
new LinkAddress("192.1.2.3/8"), null,
@@ -447,7 +455,7 @@
// Check whether return address is under prefix 172.16.0.0/12.
final LinkAddress classB1 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.31.43.42/24"), classB1);
final UpstreamNetworkState mobileUpstream2 = buildUpstreamNetworkState(mMobileNetwork2,
new LinkAddress("172.28.123.100/14"), null,
@@ -458,12 +466,12 @@
// 172.28.0.0 ~ 172.31.255.255 is not available.
// Check whether return address is next address of prefix 172.16.0.0/14.
final LinkAddress classB2 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.16.0.42/24"), classB2);
// Check whether new downstream is next address of address 172.16.0.42/24.
final LinkAddress classB3 = requestDownstreamAddress(mUsbIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.16.1.42/24"), classB3);
final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
new LinkAddress("172.16.0.1/24"), null,
@@ -474,7 +482,7 @@
// Check whether return address is next address of prefix 172.16.1.42/24.
final LinkAddress classB4 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.16.2.42/24"), classB4);
final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
new LinkAddress("172.16.0.1/13"), null,
@@ -485,11 +493,11 @@
// Check whether return address is next address of prefix 172.16.0.1/13.
final LinkAddress classB5 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.24.0.42/24"), classB5);
// Check whether return address is next address of prefix 172.24.0.42/24.
final LinkAddress classB6 = requestDownstreamAddress(mUsbIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.24.1.42/24"), classB6);
final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
new LinkAddress("172.24.0.1/12"), null,
@@ -500,11 +508,11 @@
// Check whether return address is prefix 10.0.0.0/8 + subAddress 0.31.43.42.
final LinkAddress classA1 = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("10.31.43.42/24"), classA1);
// Check whether new downstream is next address of address 10.31.43.42/24.
final LinkAddress classA2 = requestDownstreamAddress(mUsbIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("10.31.44.42/24"), classA2);
}
@@ -524,7 +532,7 @@
private void assertReseveredWifiP2pPrefix() throws Exception {
LinkAddress address = requestDownstreamAddress(mHotspotIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
final IpPrefix hotspotPrefix = asIpPrefix(address);
final IpPrefix legacyWifiP2pPrefix = asIpPrefix(mLegacyWifiP2pAddress);
assertNotEquals(legacyWifiP2pPrefix, hotspotPrefix);
@@ -544,8 +552,23 @@
// If #shouldEnableWifiP2pDedicatedIp() is enabled, wifi P2P gets the configured address.
LinkAddress address = requestDownstreamAddress(mWifiP2pIpServer,
- true /* useLastAddress */);
+ CONNECTIVITY_SCOPE_LOCAL, true /* useLastAddress */);
assertEquals(mLegacyWifiP2pAddress, address);
mPrivateAddressCoordinator.releaseDownstream(mWifiP2pIpServer);
}
+
+ @Test
+ public void testEnableSapAndLohsConcurrently() throws Exception {
+ // 0x2b05 -> 43.5, 0x8605 -> 134.5
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(0x2b05, 0x8605);
+
+ final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
+ CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
+ assertEquals("Wrong hotspot prefix: ", new LinkAddress("192.168.43.5/24"), hotspotAddress);
+
+ final LinkAddress localHotspotAddress = requestDownstreamAddress(mLocalHotspotIpServer,
+ CONNECTIVITY_SCOPE_LOCAL, true /* useLastAddress */);
+ assertEquals("Wrong local hotspot prefix: ", new LinkAddress("192.168.134.5/24"),
+ localHotspotAddress);
+ }
}
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 839ca40..245ad7a 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -412,10 +412,8 @@
// Always allow and never count clat traffic. Only the IPv4 traffic on the stacked
// interface is accounted for and subject to usage restrictions.
- // TODO: remove sock_uid check once Nat464Xlat javaland adds the socket tag AID_CLAT for clat.
- if (sock_uid == AID_CLAT || uid == AID_CLAT) {
- return PASS;
- }
+ // CLAT IPv6 TX sockets are *always* tagged with CLAT uid, see tagSocketAsClat()
+ if (uid == AID_CLAT) return PASS;
int match = bpf_owner_match(skb, sock_uid, egress, kver);
@@ -441,17 +439,17 @@
uint32_t mapSettingKey = CURRENT_STATS_MAP_CONFIGURATION_KEY;
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
- // that the returned value is always BPF_NOMATCH(0) or BPF_MATCH(1).
- if (!selectedMap) {
- asm("%0 &= 1" : "+r"(match));
- return match;
- }
+ if (!selectedMap) return PASS; // cannot happen, needed to keep bpf verifier happy
do_packet_tracing(skb, egress, uid, tag, enable_tracing, kver);
update_stats_with_config(*selectedMap, skb, &key, egress, kver);
update_app_uid_stats_map(skb, &uid, egress, kver);
+
+ // We've already handled DROP_UNLESS_DNS up above, thus when we reach here the only
+ // possible values of match are DROP(0) or PASS(1), however we need to use
+ // "match &= 1" before 'return match' to help the kernel's bpf verifier,
+ // so that it can be 100% certain that the returned value is always 0 or 1.
+ // We use assembly so that it cannot be optimized out by a too smart compiler.
asm("%0 &= 1" : "+r"(match));
return match;
}
@@ -502,9 +500,8 @@
// Clat daemon does not generate new traffic, all its traffic is accounted for already
// on the v4-* interfaces (except for the 20 (or 28) extra bytes of IPv6 vs IPv4 overhead,
// but that can be corrected for later when merging v4-foo stats into interface foo's).
- // TODO: remove sock_uid check once Nat464Xlat javaland adds the socket tag AID_CLAT for clat.
+ // CLAT sockets are created by system server and tagged as uid CLAT, see tagSocketAsClat()
uint32_t sock_uid = bpf_get_socket_uid(skb);
- if (sock_uid == AID_CLAT) return BPF_NOMATCH;
if (sock_uid == AID_SYSTEM) {
uint64_t cookie = bpf_get_socket_cookie(skb);
UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie);
diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java
index 822e67d..67dacb8 100644
--- a/framework/src/android/net/ConnectivitySettingsManager.java
+++ b/framework/src/android/net/ConnectivitySettingsManager.java
@@ -28,6 +28,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.net.ConnectivityManager.MultipathPreference;
import android.os.Binder;
import android.os.Build;
@@ -36,6 +37,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Range;
import com.android.net.module.util.ConnectivitySettingsUtils;
@@ -55,6 +57,7 @@
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public class ConnectivitySettingsManager {
+ private static final String TAG = ConnectivitySettingsManager.class.getSimpleName();
private ConnectivitySettingsManager() {}
@@ -696,10 +699,20 @@
/**
* Set global http proxy settings from given {@link ProxyInfo}.
*
+ * <p class="note">
+ * While a {@link ProxyInfo} for a PAC proxy can be specified, not all devices support
+ * PAC proxies. In particular, smaller devices like watches often do not have the capabilities
+ * necessary to interpret the PAC file. In such cases, calling this API with a PAC proxy
+ * results in undefined behavior, including possibly breaking networking for applications.
+ * You can test for this by checking for the presence of {@link PackageManager.FEATURE_WEBVIEW}.
+ * </p>
+ *
* @param context The {@link Context} to set the setting.
* @param proxyInfo The {@link ProxyInfo} for global http proxy settings which build from
* {@link ProxyInfo#buildPacProxy(Uri)} or
* {@link ProxyInfo#buildDirectProxy(String, int, List)}
+ * @throws UnsupportedOperationException if |proxyInfo| codes for a PAC proxy but the system
+ * does not support PAC proxies.
*/
public static void setGlobalProxy(@NonNull Context context, @NonNull ProxyInfo proxyInfo) {
final String host = proxyInfo.getHost();
@@ -707,6 +720,14 @@
final String exclusionList = proxyInfo.getExclusionListAsString();
final String pacFileUrl = proxyInfo.getPacFileUrl().toString();
+
+ if (!TextUtils.isEmpty(pacFileUrl)) {
+ final PackageManager pm = context.getPackageManager();
+ if (null != pm && !pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+ Log.wtf(TAG, "PAC proxy can't be installed on a device without FEATURE_WEBVIEW");
+ }
+ }
+
if (TextUtils.isEmpty(pacFileUrl)) {
Settings.Global.putString(context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, host);
Settings.Global.putInt(context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, port);
diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java
index 4f7ac30..9fb9bc6 100644
--- a/framework/src/android/net/LinkProperties.java
+++ b/framework/src/android/net/LinkProperties.java
@@ -1456,8 +1456,13 @@
* @hide
*/
public boolean isIdenticalPcscfs(@NonNull LinkProperties target) {
- // list order is important, compare one by one
- return target.getPcscfServers().equals(mPcscfs);
+ // Per 3GPP TS 24.229, B.2.2.1 PDP context activation and P-CSCF discovery
+ // list order is important, so on U+ compare one by one
+ if (SdkLevel.isAtLeastU()) return target.getPcscfServers().equals(mPcscfs);
+ // but for safety old behaviour on pre-U:
+ Collection<InetAddress> targetPcscfs = target.getPcscfServers();
+ return (mPcscfs.size() == targetPcscfs.size()) ?
+ mPcscfs.containsAll(targetPcscfs) : false;
}
/**
diff --git a/framework/src/android/net/SocketKeepalive.java b/framework/src/android/net/SocketKeepalive.java
index 10daf17..f915e72 100644
--- a/framework/src/android/net/SocketKeepalive.java
+++ b/framework/src/android/net/SocketKeepalive.java
@@ -149,9 +149,7 @@
public static final int ERROR_INSUFFICIENT_RESOURCES = -32;
/**
- * There was no such slot. This should only be internally as it indicates
- * a programming error in the system server. It should not propagate to
- * applications.
+ * There was no such slot, or no keepalive running on this slot.
* @hide
*/
@SystemApi
diff --git a/netd/Android.bp b/netd/Android.bp
index 473460d..4325d89 100644
--- a/netd/Android.bp
+++ b/netd/Android.bp
@@ -35,6 +35,9 @@
"BpfHandler.cpp",
"NetdUpdatable.cpp",
],
+ static_libs: [
+ "libmodules-utils-build",
+ ],
shared_libs: [
"libbase",
"liblog",
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 8081d12..3984249 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -19,8 +19,10 @@
#include "BpfHandler.h"
#include <linux/bpf.h>
+#include <inttypes.h>
#include <android-base/unique_fd.h>
+#include <android-modules-utils/sdk_level.h>
#include <bpf/WaitForProgsLoaded.h>
#include <log/log.h>
#include <netdutils/UidConstants.h>
@@ -74,9 +76,11 @@
}
static Status initPrograms(const char* cg2_path) {
+ if (modules::sdklevel::IsAtLeastU() && !!strcmp(cg2_path, "/sys/fs/cgroup")) abort();
+
unique_fd cg_fd(open(cg2_path, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
if (cg_fd == -1) {
- int ret = errno;
+ const int ret = errno;
ALOGE("Failed to open the cgroup directory: %s", strerror(ret));
return statusFromErrno(ret, "Open the cgroup directory failed");
}
@@ -243,6 +247,8 @@
mCookieTagMap.getMap().get());
return -res.error().code();
}
+ ALOGD("Socket with cookie %" PRIu64 " tagged successfully with tag %" PRIu32 " uid %u "
+ "and real uid %u", sock_cookie, tag, chargeUid, realUid);
return 0;
}
@@ -256,6 +262,7 @@
ALOGE("Failed to untag socket: %s", strerror(res.error().code()));
return -res.error().code();
}
+ ALOGD("Socket with cookie %" PRIu64 " untagged successfully.", sock_cookie);
return 0;
}
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 29a03d1..47a1022 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -73,7 +73,6 @@
import com.android.server.connectivity.mdns.MdnsSearchOptions;
import com.android.server.connectivity.mdns.MdnsServiceBrowserListener;
import com.android.server.connectivity.mdns.MdnsServiceInfo;
-import com.android.server.connectivity.mdns.MdnsSocketClientBase;
import com.android.server.connectivity.mdns.MdnsSocketProvider;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -620,6 +619,7 @@
final MdnsSearchOptions.Builder optionsBuilder =
MdnsSearchOptions.newBuilder()
.setNetwork(info.getNetwork())
+ .setRemoveExpiredService(true)
.setIsPassiveMode(true);
if (typeAndSubtype.second != null) {
// The parsing ensures subtype starts with an underscore.
@@ -814,6 +814,7 @@
.setNetwork(info.getNetwork())
.setIsPassiveMode(true)
.setResolveInstanceName(info.getServiceName())
+ .setRemoveExpiredService(true)
.build();
mMdnsDiscoveryManager.registerListener(
resolveServiceType, listener, options);
@@ -907,6 +908,7 @@
.setNetwork(info.getNetwork())
.setIsPassiveMode(true)
.setResolveInstanceName(info.getServiceName())
+ .setRemoveExpiredService(true)
.build();
mMdnsDiscoveryManager.registerListener(
resolveServiceType, listener, options);
@@ -1394,8 +1396,7 @@
mMdnsSocketClient =
new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider);
mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
- mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"),
- handler.getLooper());
+ mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"));
handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
mAdvertiser = deps.makeMdnsAdvertiser(handler.getLooper(), mMdnsSocketProvider,
new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"));
@@ -1453,9 +1454,8 @@
*/
public MdnsDiscoveryManager makeMdnsDiscoveryManager(
@NonNull ExecutorProvider executorProvider,
- @NonNull MdnsSocketClientBase socketClient, @NonNull SharedLog sharedLog,
- @NonNull Looper looper) {
- return new MdnsDiscoveryManager(executorProvider, socketClient, sharedLog, looper);
+ @NonNull MdnsMultinetworkSocketClient socketClient, @NonNull SharedLog sharedLog) {
+ return new MdnsDiscoveryManager(executorProvider, socketClient, sharedLog);
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index 9a67007..84faf12 100644
--- a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -24,6 +24,7 @@
import android.util.Pair;
import com.android.server.connectivity.mdns.util.MdnsLogger;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.lang.ref.WeakReference;
@@ -75,6 +76,8 @@
private final boolean sendDiscoveryQueries;
@NonNull
private final List<MdnsResponse> servicesToResolve;
+ @NonNull
+ private final MdnsResponseDecoder.Clock clock;
EnqueueMdnsQueryCallable(
@NonNull MdnsSocketClientBase requestSender,
@@ -85,7 +88,8 @@
int transactionId,
@Nullable Network network,
boolean sendDiscoveryQueries,
- @NonNull Collection<MdnsResponse> servicesToResolve) {
+ @NonNull Collection<MdnsResponse> servicesToResolve,
+ @NonNull MdnsResponseDecoder.Clock clock) {
weakRequestSender = new WeakReference<>(requestSender);
this.packetWriter = packetWriter;
serviceTypeLabels = TextUtils.split(serviceType, "\\.");
@@ -95,6 +99,7 @@
this.network = network;
this.sendDiscoveryQueries = sendDiscoveryQueries;
this.servicesToResolve = new ArrayList<>(servicesToResolve);
+ this.clock = clock;
}
// Incompatible return type for override of Callable#call().
@@ -119,22 +124,24 @@
// List of (name, type) to query
final ArrayList<Pair<String[], Integer>> missingKnownAnswerRecords = new ArrayList<>();
+ final long now = clock.elapsedRealtime();
for (MdnsResponse response : servicesToResolve) {
- // TODO: also send queries to renew record TTL (as per RFC6762 7.1 no need to query
- // if remaining TTL is more than half the original one, so send the queries if half
- // the TTL has passed).
- if (response.isComplete()) continue;
final String[] serviceName = response.getServiceName();
if (serviceName == null) continue;
- if (!response.hasTextRecord()) {
+ if (!response.hasTextRecord() || MdnsUtils.isRecordRenewalNeeded(
+ response.getTextRecord(), now)) {
missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_TXT));
}
- if (!response.hasServiceRecord()) {
+ if (!response.hasServiceRecord() || MdnsUtils.isRecordRenewalNeeded(
+ response.getServiceRecord(), now)) {
missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_SRV));
// The hostname is not yet known, so queries for address records will be sent
// the next time the EnqueueMdnsQueryCallable is enqueued if the reply does not
// contain them. In practice, advertisers should include the address records
// when queried for SRV, although it's not a MUST requirement (RFC6763 12.2).
+ // TODO: Figure out how to renew the A/AAAA record. Usually A/AAAA record will
+ // be included in the response to the SRV record so in high chances there is
+ // no need to renew them individually.
} else if (!response.hasInet4AddressRecord() && !response.hasInet6AddressRecord()) {
final String[] host = response.getServiceRecord().getServiceHost();
missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_A));
@@ -211,7 +218,7 @@
| (expectUnicastResponse ? MdnsConstants.QCLASS_UNICAST : 0));
}
- private void sendPacketTo(MdnsSocketClientBase requestSender, InetSocketAddress address)
+ private void sendPacketTo(MdnsSocketClient requestSender, InetSocketAddress address)
throws IOException {
DatagramPacket packet = packetWriter.getPacket(address);
if (expectUnicastResponse) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java b/service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java
index 75c7e6c..761c477 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java
@@ -94,10 +94,6 @@
return false;
}
- public static boolean allowSearchOptionsToRemoveExpiredService() {
- return false;
- }
-
public static boolean allowNetworkInterfaceIndexPropagation() {
return true;
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index 0154827..d3cbf82 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -17,8 +17,6 @@
package com.android.server.connectivity.mdns;
import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
-import static com.android.server.connectivity.mdns.util.MdnsUtils.isNetworkMatched;
-import static com.android.server.connectivity.mdns.util.MdnsUtils.isRunningOnHandlerThread;
import android.Manifest.permission;
import android.annotation.NonNull;
@@ -26,7 +24,7 @@
import android.annotation.RequiresPermission;
import android.net.Network;
import android.os.Handler;
-import android.os.Looper;
+import android.os.HandlerThread;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
@@ -38,6 +36,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* This class keeps tracking the set of registered {@link MdnsServiceBrowserListener} instances, and
@@ -53,6 +52,7 @@
@NonNull private final PerNetworkServiceTypeClients perNetworkServiceTypeClients;
@NonNull private final Handler handler;
+ @Nullable private final HandlerThread handlerThread;
private static class PerNetworkServiceTypeClients {
private final ArrayMap<Pair<String, Network>, MdnsServiceTypeClient> clients =
@@ -86,15 +86,12 @@
return list;
}
- public List<MdnsServiceTypeClient> getByMatchingNetwork(@Nullable Network network) {
+ public List<MdnsServiceTypeClient> getByNetwork(@Nullable Network network) {
final List<MdnsServiceTypeClient> list = new ArrayList<>();
for (int i = 0; i < clients.size(); i++) {
final Pair<String, Network> perNetworkServiceType = clients.keyAt(i);
final Network serviceTypeNetwork = perNetworkServiceType.second;
- // The serviceTypeNetwork would be null if the MdnsSocketClient is being used. This
- // is also the case if the socket is for a tethering interface. In either of these
- // cases, the client is expected to process any responses.
- if (serviceTypeNetwork == null || isNetworkMatched(network, serviceTypeNetwork)) {
+ if (Objects.equals(network, serviceTypeNetwork)) {
list.add(clients.valueAt(i));
}
}
@@ -112,17 +109,23 @@
}
public MdnsDiscoveryManager(@NonNull ExecutorProvider executorProvider,
- @NonNull MdnsSocketClientBase socketClient, @NonNull SharedLog sharedLog,
- @NonNull Looper looper) {
+ @NonNull MdnsSocketClientBase socketClient, @NonNull SharedLog sharedLog) {
this.executorProvider = executorProvider;
this.socketClient = socketClient;
this.sharedLog = sharedLog;
- perNetworkServiceTypeClients = new PerNetworkServiceTypeClients();
- handler = new Handler(looper);
+ this.perNetworkServiceTypeClients = new PerNetworkServiceTypeClients();
+ if (socketClient.getLooper() != null) {
+ this.handlerThread = null;
+ this.handler = new Handler(socketClient.getLooper());
+ } else {
+ this.handlerThread = new HandlerThread(MdnsDiscoveryManager.class.getSimpleName());
+ this.handlerThread.start();
+ this.handler = new Handler(handlerThread.getLooper());
+ }
}
private void checkAndRunOnHandlerThread(@NonNull Runnable function) {
- if (isRunningOnHandlerThread(handler)) {
+ if (this.handlerThread == null) {
function.run();
} else {
handler.post(function);
@@ -130,6 +133,15 @@
}
/**
+ * Do the cleanup of the MdnsDiscoveryManager
+ */
+ public void shutDown() {
+ if (this.handlerThread != null) {
+ this.handlerThread.quitSafely();
+ }
+ }
+
+ /**
* Starts (or continue) to discovery mDNS services with given {@code serviceType}, and registers
* {@code listener} for receiving mDNS service discovery responses.
*
@@ -239,11 +251,19 @@
private void handleOnResponseReceived(@NonNull MdnsPacket packet, int interfaceIndex,
@Nullable Network network) {
for (MdnsServiceTypeClient serviceTypeClient
- : perNetworkServiceTypeClients.getByMatchingNetwork(network)) {
+ : getMdnsServiceTypeClient(network)) {
serviceTypeClient.processResponse(packet, interfaceIndex, network);
}
}
+ private List<MdnsServiceTypeClient> getMdnsServiceTypeClient(@Nullable Network network) {
+ if (socketClient.supportsRequestingSpecificNetworks()) {
+ return perNetworkServiceTypeClients.getByNetwork(network);
+ } else {
+ return perNetworkServiceTypeClients.getByNetwork(null);
+ }
+ }
+
@Override
public void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode,
@Nullable Network network) {
@@ -254,7 +274,7 @@
private void handleOnFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode,
@Nullable Network network) {
for (MdnsServiceTypeClient serviceTypeClient
- : perNetworkServiceTypeClients.getByMatchingNetwork(network)) {
+ : getMdnsServiceTypeClient(network)) {
serviceTypeClient.onFailedToParseMdnsResponse(receivedPacketNumber, errorCode);
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index 52c8622..73e4497 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -17,7 +17,6 @@
package com.android.server.connectivity.mdns;
import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
-import static com.android.server.connectivity.mdns.util.MdnsUtils.isNetworkMatched;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,6 +33,7 @@
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.util.List;
+import java.util.Objects;
/**
* The {@link MdnsMultinetworkSocketClient} manages the multinetwork socket for mDns
@@ -205,6 +205,16 @@
mSocketProvider.unrequestSocket(callback);
}
+ @Override
+ public Looper getLooper() {
+ return mHandler.getLooper();
+ }
+
+ @Override
+ public boolean supportsRequestingSpecificNetworks() {
+ return true;
+ }
+
private void sendMdnsPacket(@NonNull DatagramPacket packet, @Nullable Network targetNetwork) {
final boolean isIpv6 = ((InetSocketAddress) packet.getSocketAddress()).getAddress()
instanceof Inet6Address;
@@ -216,7 +226,10 @@
final Network network = activeSockets.valueAt(i);
// Check ip capability and network before sending packet
if (((isIpv6 && socket.hasJoinedIpv6()) || (isIpv4 && socket.hasJoinedIpv4()))
- && isNetworkMatched(targetNetwork, network)) {
+ // Contrary to MdnsUtils.isNetworkMatched, only send packets targeting
+ // the null network to interfaces that have the null network (tethering
+ // downstream interfaces).
+ && Objects.equals(network, targetNetwork)) {
try {
socket.send(packet);
} catch (IOException e) {
@@ -248,12 +261,6 @@
}
}
- /** Sends a mDNS request packet that asks for multicast response. */
- @Override
- public void sendMulticastPacket(@NonNull DatagramPacket packet) {
- sendMulticastPacket(packet, null /* network */);
- }
-
/**
* Sends a mDNS request packet via given network that asks for multicast response. Null network
* means sending packet via all networks.
@@ -263,12 +270,6 @@
mHandler.post(() -> sendMdnsPacket(packet, network));
}
- /** Sends a mDNS request packet that asks for unicast response. */
- @Override
- public void sendUnicastPacket(@NonNull DatagramPacket packet) {
- sendUnicastPacket(packet, null /* network */);
- }
-
/**
* Sends a mDNS request packet via given network that asks for unicast response. Null network
* means sending packet via all networks.
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index e56bd5b..809750d 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -66,9 +66,6 @@
private final Map<String, MdnsResponse> instanceNameToResponse = new HashMap<>();
private final boolean removeServiceAfterTtlExpires =
MdnsConfigs.removeServiceAfterTtlExpires();
- private final boolean allowSearchOptionsToRemoveExpiredService =
- MdnsConfigs.allowSearchOptionsToRemoveExpiredService();
-
private final MdnsResponseDecoder.Clock clock;
@Nullable private MdnsSearchOptions searchOptions;
@@ -201,7 +198,7 @@
searchOptions.getSubtypes(),
searchOptions.isPassiveMode(),
++currentSessionId,
- searchOptions.getNetwork());
+ network);
if (hadReply) {
requestTaskFuture = scheduleNextRunLocked(taskConfig);
} else {
@@ -374,9 +371,7 @@
if (removeServiceAfterTtlExpires) {
return true;
}
- return allowSearchOptionsToRemoveExpiredService
- && searchOptions != null
- && searchOptions.removeExpiredService();
+ return searchOptions != null && searchOptions.removeExpiredService();
}
@VisibleForTesting
@@ -537,7 +532,8 @@
config.transactionId,
config.network,
sendDiscoveryQueries,
- servicesToResolve)
+ servicesToResolve,
+ clock)
.call();
} catch (RuntimeException e) {
sharedLog.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
index 783b18a..b982644 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -195,13 +195,11 @@
}
/** Sends a mDNS request packet that asks for multicast response. */
- @Override
public void sendMulticastPacket(@NonNull DatagramPacket packet) {
sendMdnsPacket(packet, multicastPacketQueue);
}
/** Sends a mDNS request packet that asks for unicast response. */
- @Override
public void sendUnicastPacket(DatagramPacket packet) {
if (useSeparateSocketForUnicast) {
sendMdnsPacket(packet, unicastPacketQueue);
@@ -210,6 +208,41 @@
}
}
+ @Override
+ public void sendMulticastPacket(@NonNull DatagramPacket packet, @Nullable Network network) {
+ if (network != null) {
+ throw new IllegalArgumentException("This socket client does not support sending to "
+ + "specific networks");
+ }
+ sendMulticastPacket(packet);
+ }
+
+ @Override
+ public void sendUnicastPacket(@NonNull DatagramPacket packet, @Nullable Network network) {
+ if (network != null) {
+ throw new IllegalArgumentException("This socket client does not support sending to "
+ + "specific networks");
+ }
+ sendUnicastPacket(packet);
+ }
+
+ @Override
+ public void notifyNetworkRequested(
+ @NonNull MdnsServiceBrowserListener listener,
+ @Nullable Network network,
+ @NonNull SocketCreationCallback socketCreationCallback) {
+ if (network != null) {
+ throw new IllegalArgumentException("This socket client does not support requesting "
+ + "specific networks");
+ }
+ socketCreationCallback.onSocketCreated(null);
+ }
+
+ @Override
+ public boolean supportsRequestingSpecificNetworks() {
+ return false;
+ }
+
private void sendMdnsPacket(DatagramPacket packet, Queue<DatagramPacket> packetQueueToUse) {
if (shouldStopSocketLoop && !MdnsConfigs.allowAddMdnsPacketAfterDiscoveryStops()) {
LOGGER.w("sendMdnsPacket() is called after discovery already stopped");
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
index c614cd3..e0762f9 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Network;
+import android.os.Looper;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -38,39 +39,37 @@
/*** Set callback for receiving mDns response */
void setCallback(@Nullable Callback callback);
- /*** Sends a mDNS request packet that asks for multicast response. */
- void sendMulticastPacket(@NonNull DatagramPacket packet);
+ /**
+ * Send a mDNS request packet via given network that asks for multicast response.
+ *
+ * <p>The socket client may use a null network to identify some or all interfaces, in which case
+ * passing null sends the packet to these.
+ */
+ void sendMulticastPacket(@NonNull DatagramPacket packet, @Nullable Network network);
/**
- * Sends a mDNS request packet via given network that asks for multicast response. Null network
- * means sending packet via all networks.
+ * Send a mDNS request packet via given network that asks for unicast response.
+ *
+ * <p>The socket client may use a null network to identify some or all interfaces, in which case
+ * passing null sends the packet to these.
*/
- default void sendMulticastPacket(@NonNull DatagramPacket packet, @Nullable Network network) {
- throw new UnsupportedOperationException(
- "This socket client doesn't support per-network sending");
- }
-
- /*** Sends a mDNS request packet that asks for unicast response. */
- void sendUnicastPacket(@NonNull DatagramPacket packet);
-
- /**
- * Sends a mDNS request packet via given network that asks for unicast response. Null network
- * means sending packet via all networks.
- */
- default void sendUnicastPacket(@NonNull DatagramPacket packet, @Nullable Network network) {
- throw new UnsupportedOperationException(
- "This socket client doesn't support per-network sending");
- }
+ void sendUnicastPacket(@NonNull DatagramPacket packet, @Nullable Network network);
/*** Notify that the given network is requested for mdns discovery / resolution */
- default void notifyNetworkRequested(@NonNull MdnsServiceBrowserListener listener,
- @Nullable Network network, @NonNull SocketCreationCallback socketCreationCallback) {
- socketCreationCallback.onSocketCreated(network);
- }
+ void notifyNetworkRequested(@NonNull MdnsServiceBrowserListener listener,
+ @Nullable Network network, @NonNull SocketCreationCallback socketCreationCallback);
/*** Notify that the network is unrequested */
default void notifyNetworkUnrequested(@NonNull MdnsServiceBrowserListener listener) { }
+ /*** Gets looper that used by the socket client */
+ default Looper getLooper() {
+ return null;
+ }
+
+ /** Returns whether the socket client support requesting per network */
+ boolean supportsRequestingSpecificNetworks();
+
/*** Callback for mdns response */
interface Callback {
/*** Receive a mdns response */
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
index 2fa1ae4..dc09bef 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -25,7 +25,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkAddress;
@@ -35,6 +38,9 @@
import android.net.NetworkRequest;
import android.net.TetheringManager;
import android.net.TetheringManager.TetheringEventCallback;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pInfo;
+import android.net.wifi.p2p.WifiP2pManager;
import android.os.Handler;
import android.os.Looper;
import android.util.ArrayMap;
@@ -51,6 +57,7 @@
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* The {@link MdnsSocketProvider} manages the multiple sockets for mDns.
@@ -92,6 +99,60 @@
private final byte[] mPacketReadBuffer = new byte[READ_BUFFER_SIZE];
private boolean mMonitoringSockets = false;
private boolean mRequestStop = false;
+ private String mWifiP2pTetherInterface = null;
+
+ private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String newP2pIface = getWifiP2pInterface(intent);
+
+ if (!mMonitoringSockets || !hasAllNetworksRequest()) {
+ mWifiP2pTetherInterface = newP2pIface;
+ return;
+ }
+
+ // If already serving from the correct interface, nothing to do.
+ if (Objects.equals(mWifiP2pTetherInterface, newP2pIface)) return;
+
+ if (mWifiP2pTetherInterface != null) {
+ if (newP2pIface != null) {
+ Log.wtf(TAG, "Wifi p2p interface is changed from " + mWifiP2pTetherInterface
+ + " to " + newP2pIface + " without null broadcast");
+ }
+ // Remove the socket.
+ removeTetherInterfaceSocket(mWifiP2pTetherInterface);
+ }
+
+ // Update mWifiP2pTetherInterface
+ mWifiP2pTetherInterface = newP2pIface;
+
+ // Check whether the socket for wifi p2p interface is created or not.
+ final boolean socketAlreadyExists = mTetherInterfaceSockets.get(newP2pIface) != null;
+ if (newP2pIface != null && !socketAlreadyExists) {
+ // Create a socket for wifi p2p interface.
+ final int ifaceIndex =
+ mDependencies.getNetworkInterfaceIndexByName(newP2pIface);
+ createSocket(LOCAL_NET, createLPForTetheredInterface(newP2pIface, ifaceIndex));
+ }
+ }
+ };
+
+ @Nullable
+ private static String getWifiP2pInterface(final Intent intent) {
+ final WifiP2pGroup group =
+ intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP);
+ final WifiP2pInfo p2pInfo =
+ intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO);
+ if (group == null || p2pInfo == null) {
+ return null;
+ }
+
+ if (!p2pInfo.groupFormed) {
+ return null;
+ } else {
+ return group.getInterface();
+ }
+ }
public MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper,
@NonNull SharedLog sharedLog) {
@@ -138,6 +199,18 @@
mSocketNetlinkMonitor = mDependencies.createSocketNetlinkMonitor(mHandler,
mSharedLog.forSubComponent("NetlinkMonitor"), new NetLinkMessageProcessor());
+
+ // Register a intent receiver to listen wifi p2p interface changes.
+ // Note: The wifi p2p interface change is only notified via
+ // TetheringEventCallback#onLocalOnlyInterfacesChanged if the device is the wifi p2p group
+ // owner. In this case, MdnsSocketProvider will receive duplicate interface changes and must
+ // ignore the later notification because the socket has already been created. There is only
+ // one notification from the wifi p2p connection change intent if the device is not the wifi
+ // p2p group owner.
+ final IntentFilter intentFilter =
+ new IntentFilter(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+ mContext.registerReceiver(
+ mIntentReceiver, intentFilter, null /* broadcastPermission */, mHandler);
}
/**
@@ -376,17 +449,28 @@
if (!hasAllNetworksRequest()) {
// Currently, the network for tethering can not be requested, so the sockets for
// tethering are only created if there is a request for all networks (interfaces).
- // Therefore, this change can skip if there is no such request.
+ // Therefore, only update the interface list and skip this change if no such request.
if (DBG) {
Log.d(TAG, "Ignore tether interfaces change. There is no request for all"
+ " networks.");
}
+ current.clear();
+ current.addAll(updated);
return;
}
final CompareResult<String> interfaceDiff = new CompareResult<>(
current, updated);
for (String name : interfaceDiff.added) {
+ // Check if a socket has been created for the interface
+ final SocketInfo socketInfo = mTetherInterfaceSockets.get(name);
+ if (socketInfo != null) {
+ if (DBG) {
+ mSharedLog.i("Socket is existed for interface:" + name);
+ }
+ continue;
+ }
+
int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(name);
createSocket(LOCAL_NET, createLPForTetheredInterface(name, ifaceIndex));
}
@@ -580,6 +664,11 @@
for (String tetheredInterface : mTetheredInterfaces) {
retrieveAndNotifySocketFromInterface(tetheredInterface, cb);
}
+
+ if (mWifiP2pTetherInterface != null
+ && !mLocalOnlyInterfaces.contains(mWifiP2pTetherInterface)) {
+ retrieveAndNotifySocketFromInterface(mWifiP2pTetherInterface, cb);
+ }
} else {
retrieveAndNotifySocketFromNetwork(network, cb);
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java b/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
index 40191cf..451909c 100644
--- a/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
+++ b/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
@@ -61,13 +61,11 @@
final StructIfaddrMsg ifaddrMsg = msg.getIfaddrHeader();
final LinkAddress la = new LinkAddress(msg.getIpAddress(), ifaddrMsg.prefixLen,
msg.getFlags(), ifaddrMsg.scope);
- if (!la.isPreferred()) {
- // Skip the unusable ip address.
- return;
- }
switch (msg.getHeader().nlmsg_type) {
case NetlinkConstants.RTM_NEWADDR:
- mCb.addOrUpdateInterfaceAddress(ifaddrMsg.index, la);
+ if (la.isPreferred()) {
+ mCb.addOrUpdateInterfaceAddress(ifaddrMsg.index, la);
+ }
break;
case NetlinkConstants.RTM_DELADDR:
mCb.deleteInterfaceAddress(ifaddrMsg.index, la);
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index bc94869..eb12b9a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -152,4 +152,15 @@
encoder.encode(CharBuffer.wrap(originalName), out, true /* endOfInput */);
return new String(out.array(), 0, out.position(), utf8);
}
+
+ /**
+ * Checks if the MdnsRecord needs to be renewed or not.
+ *
+ * <p>As per RFC6762 7.1 no need to query if remaining TTL is more than half the original one,
+ * so send the queries if half the TTL has passed.
+ */
+ public static boolean isRecordRenewalNeeded(@NonNull MdnsRecord mdnsRecord, final long now) {
+ return mdnsRecord.getTtl() > 0
+ && mdnsRecord.getRemainingTTL(now) <= mdnsRecord.getTtl() / 2;
+ }
}
\ No newline at end of file
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index d966070..c125bd6 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -475,7 +475,7 @@
static void com_android_server_connectivity_ClatCoordinator_stopClatd(JNIEnv* env, jclass clazz,
jint pid) {
if (pid <= 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "Invalid pid");
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Invalid pid");
return;
}
diff --git a/service/libconnectivity/Android.bp b/service/libconnectivity/Android.bp
index 391ceac..e063af7 100644
--- a/service/libconnectivity/Android.bp
+++ b/service/libconnectivity/Android.bp
@@ -27,6 +27,7 @@
min_sdk_version: "30",
static_libs: [
"connectivity_native_aidl_interface-V1-ndk",
+ "libmodules-utils-build",
],
export_include_dirs: ["include"],
cflags: [
diff --git a/service/libconnectivity/src/connectivity_native.cpp b/service/libconnectivity/src/connectivity_native.cpp
index a476498..f39bb0a 100644
--- a/service/libconnectivity/src/connectivity_native.cpp
+++ b/service/libconnectivity/src/connectivity_native.cpp
@@ -17,6 +17,7 @@
#include "connectivity_native.h"
#include <android/binder_manager.h>
+#include <android-modules-utils/sdk_level.h>
#include <aidl/android/net/connectivity/aidl/ConnectivityNative.h>
using aidl::android::net::connectivity::aidl::IConnectivityNative;
@@ -44,6 +45,7 @@
}
int AConnectivityNative_blockPortForBind(in_port_t port) {
+ if (!android::modules::sdklevel::IsAtLeastU()) return ENOSYS;
std::shared_ptr<IConnectivityNative> c = getBinder();
if (!c) {
return EAGAIN;
@@ -52,6 +54,7 @@
}
int AConnectivityNative_unblockPortForBind(in_port_t port) {
+ if (!android::modules::sdklevel::IsAtLeastU()) return ENOSYS;
std::shared_ptr<IConnectivityNative> c = getBinder();
if (!c) {
return EAGAIN;
@@ -60,6 +63,7 @@
}
int AConnectivityNative_unblockAllPortsForBind() {
+ if (!android::modules::sdklevel::IsAtLeastU()) return ENOSYS;
std::shared_ptr<IConnectivityNative> c = getBinder();
if (!c) {
return EAGAIN;
@@ -68,6 +72,7 @@
}
int AConnectivityNative_getPortsBlockedForBind(in_port_t *ports, size_t *count) {
+ if (!android::modules::sdklevel::IsAtLeastU()) return ENOSYS;
std::shared_ptr<IConnectivityNative> c = getBinder();
if (!c) {
return EAGAIN;
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index c667d72..fad6aaa 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -109,7 +109,6 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.ActivityManager.UidFrozenStateChangedCallback;
@@ -318,6 +317,7 @@
import java.io.InterruptedIOException;
import java.io.PrintWriter;
import java.io.Writer;
+import java.lang.IllegalArgumentException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -442,6 +442,8 @@
private final Context mContext;
private final ConnectivityResources mResources;
+ private final int mWakeUpMark;
+ private final int mWakeUpMask;
// The Context is created for UserHandle.ALL.
private final Context mUserAllContext;
private final Dependencies mDeps;
@@ -862,8 +864,7 @@
// A helper object to track the current default HTTP proxy. ConnectivityService needs to tell
// the world when it changes.
- @VisibleForTesting
- protected final ProxyTracker mProxyTracker;
+ private final ProxyTracker mProxyTracker;
final private SettingsObserver mSettingsObserver;
@@ -1279,6 +1280,18 @@
return Binder.getCallingUid();
}
+ public boolean isAtLeastS() {
+ return SdkLevel.isAtLeastS();
+ }
+
+ public boolean isAtLeastT() {
+ return SdkLevel.isAtLeastT();
+ }
+
+ public boolean isAtLeastU() {
+ return SdkLevel.isAtLeastU();
+ }
+
/**
* Get system properties to use in ConnectivityService.
*/
@@ -1391,7 +1404,7 @@
@Nullable
public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
@NonNull final Context context, @NonNull final TelephonyManager tm) {
- if (SdkLevel.isAtLeastT()) {
+ if (isAtLeastT()) {
return new CarrierPrivilegeAuthenticator(context, tm);
} else {
return null;
@@ -1499,6 +1512,14 @@
}
/**
+ * As above but with a UID.
+ * @see CompatChanges#isChangeEnabled(long, int)
+ */
+ public boolean isChangeEnabled(final long changeId, final int uid) {
+ return CompatChanges.isChangeEnabled(changeId, uid);
+ }
+
+ /**
* Call {@link InetDiagMessage#destroyLiveTcpSockets(Set, Set)}
*
* @param ranges target uid ranges
@@ -1519,6 +1540,23 @@
throws SocketException, InterruptedIOException, ErrnoException {
InetDiagMessage.destroyLiveTcpSocketsByOwnerUids(ownerUids);
}
+
+ /**
+ * Schedule the evaluation timeout.
+ *
+ * When a network connects, it's "not evaluated" yet. Detection events cause the network
+ * to be "evaluated" (typically, validation or detection of a captive portal). If none
+ * of these events happen, this time will run out, after which the network is considered
+ * "evaluated" even if nothing happened to it. Notionally that means the system gave up
+ * on this network and considers it won't provide connectivity. In particular, that means
+ * it's when the system prefers it to cell if it's wifi and configuration says it should
+ * prefer bad wifi to cell.
+ */
+ public void scheduleEvaluationTimeout(@NonNull Handler handler,
+ @NonNull final Network network, final long delayMs) {
+ handler.sendMessageDelayed(
+ handler.obtainMessage(EVENT_INITIAL_EVALUATION_TIMEOUT, network), delayMs);
+ }
}
public ConnectivityService(Context context) {
@@ -1575,6 +1613,29 @@
mCellularRadioTimesharingCapable =
mResources.get().getBoolean(R.bool.config_cellular_radio_timesharing_capable);
+ int mark = mResources.get().getInteger(R.integer.config_networkWakeupPacketMark);
+ int mask = mResources.get().getInteger(R.integer.config_networkWakeupPacketMask);
+
+ if (SdkLevel.isAtLeastU()) {
+ // U+ default value of both mark & mask, this is the top bit of the skb->mark,
+ // see //system/netd/include/FwMark.h union Fwmark, field ingress_cpu_wakeup
+ final int defaultUMarkMask = 0x80000000; // u32
+
+ if ((mark == 0) || (mask == 0)) {
+ // simply treat unset/disabled as the default U value
+ mark = defaultUMarkMask;
+ mask = defaultUMarkMask;
+ }
+ if ((mark != defaultUMarkMask) || (mask != defaultUMarkMask)) {
+ // invalid device overlay settings
+ throw new IllegalArgumentException(
+ "Bad config_networkWakeupPacketMark/Mask " + mark + "/" + mask);
+ }
+ }
+
+ mWakeUpMark = mark;
+ mWakeUpMask = mask;
+
mNetd = netd;
mBpfNetMaps = mDeps.getBpfNetMaps(mContext, netd);
mHandlerThread = mDeps.makeHandlerThread();
@@ -1694,7 +1755,7 @@
// Even if it could, running on S would at least require mocking out the BPF map,
// otherwise the unit tests will fail on pre-T devices where the seccomp filter blocks
// the bpf syscall. http://aosp/1907693
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
mDscpPolicyTracker = new DscpPolicyTracker();
}
} catch (ErrnoException e) {
@@ -1704,13 +1765,13 @@
mIngressRateLimit = ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(
mContext);
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
mCdmps = new CompanionDeviceManagerProxyService(context);
} else {
mCdmps = null;
}
- if (SdkLevel.isAtLeastU()
+ if (mDeps.isAtLeastU()
&& mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION)) {
final UidFrozenStateChangedCallback frozenStateChangedCallback =
new UidFrozenStateChangedCallback() {
@@ -1830,6 +1891,13 @@
mHandler.sendEmptyMessage(EVENT_INGRESS_RATE_LIMIT_CHANGED);
}
+ @VisibleForTesting
+ void simulateUpdateProxyInfo(@Nullable final Network network,
+ @NonNull final ProxyInfo proxyInfo) {
+ Message.obtain(mHandler, EVENT_PROXY_HAS_CHANGED,
+ new Pair<>(network, proxyInfo)).sendToTarget();
+ }
+
private void handleAlwaysOnNetworkRequest(
NetworkRequest networkRequest, String settingName, boolean defaultValue) {
final boolean enable = toBool(Settings.Global.getInt(
@@ -3178,8 +3246,6 @@
sendStickyBroadcast(makeGeneralIntent(info, bcastType));
}
- // TODO(b/193460475): Remove when tooling supports SystemApi to public API.
- @SuppressLint("NewApi")
// TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
@TargetApi(Build.VERSION_CODES.S)
private void sendStickyBroadcast(Intent intent) {
@@ -3215,7 +3281,7 @@
private void applyMostRecentPolicyForConnectivityAction(BroadcastOptions options,
NetworkInfo info) {
// Delivery group policy APIs are only available on U+.
- if (!SdkLevel.isAtLeastU()) return;
+ if (!mDeps.isAtLeastU()) return;
final BroadcastOptionsShim optsShim = mDeps.makeBroadcastOptionsShim(options);
try {
@@ -3293,7 +3359,7 @@
}
// On T+ devices, register callback for statsd to pull NETWORK_BPF_MAP_INFO atom
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
mBpfNetMaps.setPullAtomCallback(mContext);
}
// Wait PermissionMonitor to finish the permission update. Then MultipathPolicyTracker won't
@@ -4503,7 +4569,7 @@
@VisibleForTesting
boolean shouldIgnoreValidationFailureAfterRoam(NetworkAgentInfo nai) {
// T+ devices should use unregisterAfterReplacement.
- if (SdkLevel.isAtLeastT()) return false;
+ if (mDeps.isAtLeastT()) return false;
// If the network never roamed, return false. The check below is not sufficient if time
// since boot is less than blockTimeOut, though that's extremely unlikely to happen.
@@ -4642,7 +4708,7 @@
// for an unnecessarily long time.
destroyNativeNetwork(nai);
}
- if (!nai.isCreated() && !SdkLevel.isAtLeastT()) {
+ if (!nai.isCreated() && !mDeps.isAtLeastT()) {
// Backwards compatibility: send onNetworkDestroyed even if network was never created.
// This can never run if the code above runs because shouldDestroyNativeNetwork is
// false if the network was never created.
@@ -4726,7 +4792,7 @@
}
private void checkNrisConsistency(final NetworkRequestInfo nri) {
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
for (final NetworkRequestInfo n : mNetworkRequests.values()) {
if (n.mBinder != null && n.mBinder == nri.mBinder) {
// Temporary help to debug b/194394697 ; TODO : remove this function when the
@@ -5273,8 +5339,7 @@
/** Schedule evaluation timeout */
@VisibleForTesting
public void scheduleEvaluationTimeout(@NonNull final Network network, final long delayMs) {
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(EVENT_INITIAL_EVALUATION_TIMEOUT, network), delayMs);
+ mDeps.scheduleEvaluationTimeout(mHandler, network, delayMs);
}
@Override
@@ -6317,7 +6382,7 @@
+ Arrays.toString(ranges) + "): netd command failed: " + e);
}
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
mPermissionMonitor.updateVpnLockdownUidRanges(requireVpn, ranges);
}
@@ -7111,7 +7176,7 @@
@NonNull final NetworkCapabilities networkCapabilities) {
// This check is added to fix the linter error for "current min is 30", which is not going
// to happen because Connectivity service always run in S+.
- if (!SdkLevel.isAtLeastS()) {
+ if (!mDeps.isAtLeastS()) {
Log.wtf(TAG, "Connectivity service should always run in at least SDK S");
return;
}
@@ -8049,21 +8114,18 @@
return;
}
- int mark = mResources.get().getInteger(R.integer.config_networkWakeupPacketMark);
- int mask = mResources.get().getInteger(R.integer.config_networkWakeupPacketMask);
-
// Mask/mark of zero will not detect anything interesting.
// Don't install rules unless both values are nonzero.
- if (mark == 0 || mask == 0) {
+ if (mWakeUpMark == 0 || mWakeUpMask == 0) {
return;
}
final String prefix = makeNflogPrefix(iface, nai.network.getNetworkHandle());
try {
if (add) {
- mNetd.wakeupAddInterface(iface, prefix, mark, mask);
+ mNetd.wakeupAddInterface(iface, prefix, mWakeUpMark, mWakeUpMask);
} else {
- mNetd.wakeupDelInterface(iface, prefix, mark, mask);
+ mNetd.wakeupDelInterface(iface, prefix, mWakeUpMark, mWakeUpMask);
}
} catch (Exception e) {
loge("Exception modifying wakeup packet monitoring: " + e);
@@ -8548,7 +8610,7 @@
// On T and above, allow rules are needed for all VPNs. Allow rule with null iface is a
// wildcard to allow apps to receive packets on all interfaces. This is required to accept
// incoming traffic in Lockdown mode by overriding the Lockdown blocking rule.
- return SdkLevel.isAtLeastT() && nai.isVPN() && lp != null && lp.getInterfaceName() != null;
+ return mDeps.isAtLeastT() && nai.isVPN() && lp != null && lp.getInterfaceName() != null;
}
private static UidRangeParcel[] toUidRangeStableParcels(final @NonNull Set<UidRange> ranges) {
@@ -8581,10 +8643,18 @@
}
private void maybeCloseSockets(NetworkAgentInfo nai, Set<UidRange> ranges,
- Set<Integer> exemptUids) {
+ UidRangeParcel[] uidRangeParcels, int[] exemptUids) {
if (nai.isVPN() && !nai.networkAgentConfig.allowBypass) {
try {
- mDeps.destroyLiveTcpSockets(UidRange.toIntRanges(ranges), exemptUids);
+ if (mDeps.isAtLeastU()) {
+ final Set<Integer> exemptUidSet = new ArraySet<>();
+ for (final int uid: exemptUids) {
+ exemptUidSet.add(uid);
+ }
+ mDeps.destroyLiveTcpSockets(UidRange.toIntRanges(ranges), exemptUidSet);
+ } else {
+ mNetd.socketDestroy(uidRangeParcels, exemptUids);
+ }
} catch (Exception e) {
loge("Exception in socket destroy: ", e);
}
@@ -8592,16 +8662,16 @@
}
private void updateVpnUidRanges(boolean add, NetworkAgentInfo nai, Set<UidRange> uidRanges) {
- final Set<Integer> exemptUids = new ArraySet<>();
+ int[] exemptUids = new int[2];
// TODO: Excluding VPN_UID is necessary in order to not to kill the TCP connection used
// by PPTP. Fix this by making Vpn set the owner UID to VPN_UID instead of system when
// starting a legacy VPN, and remove VPN_UID here. (b/176542831)
- exemptUids.add(VPN_UID);
- exemptUids.add(nai.networkCapabilities.getOwnerUid());
+ exemptUids[0] = VPN_UID;
+ exemptUids[1] = nai.networkCapabilities.getOwnerUid();
UidRangeParcel[] ranges = toUidRangeStableParcels(uidRanges);
// Close sockets before modifying uid ranges so that RST packets can reach to the server.
- maybeCloseSockets(nai, uidRanges, exemptUids);
+ maybeCloseSockets(nai, uidRanges, ranges, exemptUids);
try {
if (add) {
mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
@@ -8615,7 +8685,7 @@
" on netId " + nai.network.netId + ". " + e);
}
// Close sockets that established connection while requesting netd.
- maybeCloseSockets(nai, uidRanges, exemptUids);
+ maybeCloseSockets(nai, uidRanges, ranges, exemptUids);
}
private boolean isProxySetOnAnyDefaultNetwork() {
@@ -8791,21 +8861,19 @@
// else not handled
}
- // TODO(b/193460475): Remove when tooling supports SystemApi to public API.
- @SuppressLint("NewApi")
private void sendIntent(PendingIntent pendingIntent, Intent intent) {
mPendingIntentWakeLock.acquire();
try {
if (DBG) log("Sending " + pendingIntent);
final BroadcastOptions options = BroadcastOptions.makeBasic();
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
// Explicitly disallow the receiver from starting activities, to prevent apps from
// utilizing the PendingIntent as a backdoor to do this.
options.setPendingIntentBackgroundActivityLaunchAllowed(false);
}
pendingIntent.send(mContext, 0, intent, this /* onFinished */, null /* Handler */,
null /* requiredPermission */,
- SdkLevel.isAtLeastT() ? options.toBundle() : null);
+ mDeps.isAtLeastT() ? options.toBundle() : null);
} catch (PendingIntent.CanceledException e) {
if (DBG) log(pendingIntent + " was not sent, it had been canceled.");
mPendingIntentWakeLock.release();
@@ -9084,7 +9152,7 @@
private void updateProfileAllowedNetworks() {
// Netd command is not implemented before U.
- if (!SdkLevel.isAtLeastU()) return;
+ if (!mDeps.isAtLeastU()) return;
ensureRunningOnConnectivityServiceThread();
final ArrayList<NativeUidRangeConfig> configs = new ArrayList<>();
@@ -9820,13 +9888,13 @@
// 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()) {
+ if (mDeps.isAtLeastT()) {
networkAgent.networkMonitor().notifyNetworkConnected(params);
} else {
networkAgent.networkMonitor().notifyNetworkConnected(params.linkProperties,
params.networkCapabilities);
}
- final long delay = activelyPreferBadWifi()
+ final long delay = !avoidBadWifi() && activelyPreferBadWifi()
? ACTIVELY_PREFER_BAD_WIFI_INITIAL_TIMEOUT_MS
: DONT_ACTIVELY_PREFER_BAD_WIFI_INITIAL_TIMEOUT_MS;
scheduleEvaluationTimeout(networkAgent.network, delay);
@@ -11296,7 +11364,7 @@
if (um.isManagedProfile(profile.getIdentifier())) {
return true;
}
- if (SdkLevel.isAtLeastT() && dpm.getDeviceOwner() != null) return true;
+ if (mDeps.isAtLeastT() && dpm.getDeviceOwner() != null) return true;
return false;
}
@@ -11578,7 +11646,7 @@
private boolean canNetworkBeRateLimited(@NonNull final NetworkAgentInfo networkAgent) {
// Rate-limiting cannot run correctly before T because the BPF program is not loaded.
- if (!SdkLevel.isAtLeastT()) return false;
+ if (!mDeps.isAtLeastT()) return false;
final NetworkCapabilities agentCaps = networkAgent.networkCapabilities;
// Only test networks (they cannot hold NET_CAPABILITY_INTERNET) and networks that provide
@@ -12085,7 +12153,7 @@
throw new IllegalStateException(e);
}
- if (SdkLevel.isAtLeastU() && enable) {
+ if (mDeps.isAtLeastU() && enable) {
try {
closeSocketsForFirewallChainLocked(chain);
} catch (ErrnoException | SocketException | InterruptedIOException e) {
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
index e2ef981..62d79a3 100644
--- a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
@@ -18,6 +18,7 @@
import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
import static android.net.SocketKeepalive.MIN_INTERVAL_SEC;
+import static android.net.SocketKeepalive.SUCCESS;
import static android.net.SocketKeepalive.SUCCESS_PAUSED;
import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
import static android.system.OsConstants.AF_INET;
@@ -52,6 +53,7 @@
import android.system.StructTimeval;
import android.util.LocalLog;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -210,8 +212,12 @@
this.mKi = Objects.requireNonNull(ki);
mCallback = ki.mCallback;
mUnderpinnedNetwork = underpinnedNetwork;
- if (autoOnOff && mDependencies.isFeatureEnabled(AUTOMATIC_ON_OFF_KEEPALIVE_VERSION,
- true /* defaultEnabled */)) {
+ // Reading DeviceConfig will check if the calling uid and calling package name are the
+ // same. Clear calling identity to align the calling uid and package
+ final boolean enabled = BinderUtils.withCleanCallingIdentity(
+ () -> mDependencies.isFeatureEnabled(AUTOMATIC_ON_OFF_KEEPALIVE_VERSION,
+ true /* defaultEnabled */));
+ if (autoOnOff && enabled) {
mAutomaticOnOffState = STATE_ENABLED;
if (null == ki.mFd) {
throw new IllegalArgumentException("fd can't be null with automatic "
@@ -377,7 +383,11 @@
return;
}
autoKi.mAutomaticOnOffState = STATE_ENABLED;
- handleResumeKeepalive(newKi);
+ final int error = handleResumeKeepalive(newKi);
+ if (error != SUCCESS) {
+ // Failed to start the keepalive
+ cleanupAutoOnOffKeepalive(autoKi);
+ }
}
/**
@@ -398,7 +408,20 @@
* Forward to KeepaliveTracker.
*/
public void handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai, int slot, int reason) {
- mKeepaliveTracker.handleEventSocketKeepalive(nai, slot, reason);
+ if (mKeepaliveTracker.handleEventSocketKeepalive(nai, slot, reason)) return;
+
+ // The keepalive was stopped and so the autoKi should be cleaned up.
+ final AutomaticOnOffKeepalive autoKi =
+ CollectionUtils.findFirst(
+ mAutomaticOnOffKeepalives, it -> it.match(nai.network(), slot));
+ if (autoKi == null) {
+ // This may occur when the autoKi gets cleaned up elsewhere (i.e
+ // handleCheckKeepalivesStillValid) while waiting for the network agent to
+ // start the keepalive and the network agent returns an error event.
+ Log.e(TAG, "Attempt cleanup on unknown network, slot");
+ return;
+ }
+ cleanupAutoOnOffKeepalive(autoKi);
}
/**
@@ -410,6 +433,9 @@
final List<AutomaticOnOffKeepalive> matches =
CollectionUtils.filter(mAutomaticOnOffKeepalives, it -> it.mKi.getNai() == nai);
for (final AutomaticOnOffKeepalive ki : matches) {
+ if (ki.mAutomaticOnOffState == STATE_SUSPENDED) {
+ mKeepaliveTracker.finalizePausedKeepalive(ki.mKi, reason);
+ }
cleanupAutoOnOffKeepalive(ki);
}
}
@@ -421,9 +447,14 @@
*/
public void handleStartKeepalive(Message message) {
final AutomaticOnOffKeepalive autoKi = (AutomaticOnOffKeepalive) message.obj;
+ final int error = mKeepaliveTracker.handleStartKeepalive(autoKi.mKi);
+ if (error != SUCCESS) {
+ mEventLog.log("Failed to start keepalive " + autoKi.mCallback + " on "
+ + autoKi.getNetwork() + " with error " + error);
+ return;
+ }
mEventLog.log("Start keepalive " + autoKi.mCallback + " on " + autoKi.getNetwork());
mKeepaliveStatsTracker.onStartKeepalive();
- mKeepaliveTracker.handleStartKeepalive(autoKi.mKi);
// Add automatic on/off request into list to track its life cycle.
try {
@@ -439,10 +470,22 @@
}
}
- private void handleResumeKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) {
+ /**
+ * Handle resume keepalive with the given KeepaliveInfo
+ *
+ * @return SUCCESS if the keepalive is successfully starting and the error reason otherwise.
+ */
+ private int handleResumeKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) {
+ final int error = mKeepaliveTracker.handleStartKeepalive(ki);
+ if (error != SUCCESS) {
+ mEventLog.log("Failed to resume keepalive " + ki.mCallback + " on " + ki.mNai
+ + " with error " + error);
+ return error;
+ }
mKeepaliveStatsTracker.onResumeKeepalive();
- mKeepaliveTracker.handleStartKeepalive(ki);
mEventLog.log("Resumed successfully keepalive " + ki.mCallback + " on " + ki.mNai);
+
+ return SUCCESS;
}
private void handlePauseKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) {
@@ -463,7 +506,7 @@
final KeepaliveTracker.KeepaliveInfo ki = autoKi.mKi;
mKeepaliveTracker.handleStopKeepalive(ki.getNai(), ki.getSlot(), reason);
} else {
- mKeepaliveTracker.finalizePausedKeepalive(autoKi.mKi);
+ mKeepaliveTracker.finalizePausedKeepalive(autoKi.mKi, reason);
}
cleanupAutoOnOffKeepalive(autoKi);
@@ -583,8 +626,12 @@
*/
public void dump(IndentingPrintWriter pw) {
mKeepaliveTracker.dump(pw);
- final boolean featureEnabled = mDependencies.isFeatureEnabled(
- AUTOMATIC_ON_OFF_KEEPALIVE_VERSION, true /* defaultEnabled */);
+ // Reading DeviceConfig will check if the calling uid and calling package name are the same.
+ // Clear calling identity to align the calling uid and package so that it won't fail if cts
+ // would like to call dump()
+ final boolean featureEnabled = BinderUtils.withCleanCallingIdentity(
+ () -> mDependencies.isFeatureEnabled(AUTOMATIC_ON_OFF_KEEPALIVE_VERSION,
+ true /* defaultEnabled */));
pw.println("AutomaticOnOff enabled: " + featureEnabled);
pw.increaseIndent();
for (AutomaticOnOffKeepalive autoKi : mAutomaticOnOffKeepalives) {
@@ -604,7 +651,22 @@
* Forward to KeepaliveTracker.
*/
public void handleCheckKeepalivesStillValid(NetworkAgentInfo nai) {
- mKeepaliveTracker.handleCheckKeepalivesStillValid(nai);
+ ArrayList<Pair<AutomaticOnOffKeepalive, Integer>> invalidKeepalives = null;
+
+ for (final AutomaticOnOffKeepalive autoKi : mAutomaticOnOffKeepalives) {
+ if (!nai.equals(autoKi.mKi.mNai)) continue;
+ final int error = autoKi.mKi.isValid();
+ if (error != SUCCESS) {
+ if (invalidKeepalives == null) {
+ invalidKeepalives = new ArrayList<>();
+ }
+ invalidKeepalives.add(Pair.create(autoKi, error));
+ }
+ }
+ if (invalidKeepalives == null) return;
+ for (final Pair<AutomaticOnOffKeepalive, Integer> keepaliveAndError : invalidKeepalives) {
+ handleStopKeepalive(keepaliveAndError.first, keepaliveAndError.second);
+ }
}
@VisibleForTesting
@@ -837,12 +899,8 @@
* @return whether the feature is enabled
*/
public boolean isFeatureEnabled(@NonNull final String name, final boolean defaultEnabled) {
- // Reading DeviceConfig will check if the calling uid and calling package name are the
- // same. Clear calling identity to align the calling uid and package so that it won't
- // fail if cts would like to do the dump()
- return BinderUtils.withCleanCallingIdentity(() ->
- DeviceConfigUtils.isFeatureEnabled(mContext, NAMESPACE_TETHERING, name,
- DeviceConfigUtils.TETHERING_MODULE_NAME, defaultEnabled));
+ return DeviceConfigUtils.isFeatureEnabled(mContext, NAMESPACE_TETHERING, name,
+ DeviceConfigUtils.TETHERING_MODULE_NAME, defaultEnabled);
}
/**
diff --git a/service/src/com/android/server/connectivity/KeepaliveTracker.java b/service/src/com/android/server/connectivity/KeepaliveTracker.java
index 8c170bc..941b616 100644
--- a/service/src/com/android/server/connectivity/KeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveTracker.java
@@ -54,7 +54,6 @@
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
-import android.util.Pair;
import com.android.connectivity.resources.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -337,7 +336,12 @@
return SUCCESS;
}
- private int isValid() {
+ /**
+ * Checks if the keepalive info is valid to start.
+ *
+ * @return SUCCESS if the keepalive is valid and the error reason otherwise.
+ */
+ public int isValid() {
synchronized (mNai) {
int error = checkInterval();
if (error == SUCCESS) error = checkLimit();
@@ -348,11 +352,17 @@
}
}
- void start(int slot) {
+ /**
+ * Attempt to start the keepalive on the given slot.
+ *
+ * @param slot the slot to start the keepalive on.
+ * @return SUCCESS if the keepalive is successfully starting and the error reason otherwise.
+ */
+ int start(int slot) {
// BINDER_DIED can happen if the binder died before the KeepaliveInfo was created and
// the constructor set the state to BINDER_DIED. If that's the case, the KI is already
// cleaned up.
- if (BINDER_DIED == mStartedState) return;
+ if (BINDER_DIED == mStartedState) return BINDER_DIED;
mSlot = slot;
int error = isValid();
if (error == SUCCESS) {
@@ -368,7 +378,7 @@
mTcpController.startSocketMonitor(mFd, this, mSlot);
} catch (InvalidSocketException e) {
handleStopKeepalive(mNai, mSlot, ERROR_INVALID_SOCKET);
- return;
+ return ERROR_INVALID_SOCKET;
}
final TcpKeepalivePacketData tcpData = (TcpKeepalivePacketData) mPacket;
mNai.onAddTcpKeepalivePacketFilter(slot, tcpData);
@@ -377,13 +387,14 @@
break;
default:
Log.wtf(TAG, "Starting keepalive with unknown type: " + mType);
- handleStopKeepalive(mNai, mSlot, error);
- return;
+ handleStopKeepalive(mNai, mSlot, ERROR_UNSUPPORTED);
+ return ERROR_UNSUPPORTED;
}
mStartedState = STARTING;
+ return SUCCESS;
} else {
handleStopKeepalive(mNai, mSlot, error);
- return;
+ return error;
}
}
@@ -444,6 +455,8 @@
}
}
+ // TODO: This does not clean up the autoKi in AutomaticOnOffKeepaliveTracker and it is not
+ // possible without a big refactor.
void onFileDescriptorInitiatedStop(final int socketKeepaliveReason) {
handleStopKeepalive(mNai, mSlot, socketKeepaliveReason);
}
@@ -486,12 +499,15 @@
/**
* Handle start keepalives with the message.
+ *
+ * @param ki the keepalive to start.
+ * @return SUCCESS if the keepalive is successfully starting and the error reason otherwise.
*/
- public void handleStartKeepalive(KeepaliveInfo ki) {
+ public int handleStartKeepalive(KeepaliveInfo ki) {
NetworkAgentInfo nai = ki.getNai();
int slot = findFirstFreeSlot(nai);
mKeepalives.get(nai).put(slot, ki);
- ki.start(slot);
+ return ki.start(slot);
}
public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) {
@@ -569,7 +585,20 @@
} else if (reason == ERROR_STOP_REASON_UNINITIALIZED) {
throw new IllegalStateException("Unexpected stop reason: " + reason);
} else if (reason == ERROR_NO_SUCH_SLOT) {
- throw new IllegalStateException("No such slot: " + reason);
+ // There are multiple independent reasons a keepalive can stop. Some
+ // are software (e.g. the app stops the keepalive) and some are hardware
+ // (e.g. the SIM card gets removed). Therefore, there is a very low
+ // probability that both of these happen at the same time, which would
+ // result in the first stop attempt returning SUCCESS and the second
+ // stop attempt returning NO_SUCH_SLOT. Such a race condition can be
+ // ignored with a log.
+ // This should still be reported because if it happens with any frequency
+ // it probably means there is a bug where the system server is trying
+ // to use a non-existing hardware slot.
+ // TODO : separate the non-existing hardware slot from the case where
+ // there is no keepalive running on this slot.
+ Log.wtf(TAG, "Keepalive on slot " + slot + " can't be stopped : " + reason);
+ notifyErrorCallback(ki.mCallback, reason);
} else {
notifyErrorCallback(ki.mCallback, reason);
}
@@ -580,40 +609,33 @@
/**
* Finalize a paused keepalive.
*
- * This will simply send the onStopped() callback after checking that this keepalive is
- * indeed paused.
+ * This will send the appropriate callback after checking that this keepalive is indeed paused.
*
* @param ki the keepalive to finalize
+ * @param reason the reason the keepalive is stopped
*/
- public void finalizePausedKeepalive(@NonNull final KeepaliveInfo ki) {
+ public void finalizePausedKeepalive(@NonNull final KeepaliveInfo ki, int reason) {
if (SUCCESS_PAUSED != ki.mStopReason) {
throw new IllegalStateException("Keepalive is not paused");
}
- try {
- ki.mCallback.onStopped();
- } catch (RemoteException e) {
- Log.w(TAG, "Discarded onStopped callback while finalizing paused keepalive");
+ if (reason == SUCCESS) {
+ try {
+ ki.mCallback.onStopped();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Discarded onStopped callback while finalizing paused keepalive");
+ }
+ } else {
+ notifyErrorCallback(ki.mCallback, reason);
}
}
- public void handleCheckKeepalivesStillValid(NetworkAgentInfo nai) {
- HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
- if (networkKeepalives != null) {
- ArrayList<Pair<Integer, Integer>> invalidKeepalives = new ArrayList<>();
- for (int slot : networkKeepalives.keySet()) {
- int error = networkKeepalives.get(slot).isValid();
- if (error != SUCCESS) {
- invalidKeepalives.add(Pair.create(slot, error));
- }
- }
- for (Pair<Integer, Integer> slotAndError: invalidKeepalives) {
- handleStopKeepalive(nai, slotAndError.first, slotAndError.second);
- }
- }
- }
-
- /** Handle keepalive events from lower layer. */
- public void handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai, int slot, int reason) {
+ /**
+ * Handle keepalive events from lower layer.
+ *
+ * @return false if the event caused handleStopKeepalive to be called, i.e. the keepalive is
+ * forced to stop. Otherwise, return true.
+ */
+ public boolean handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai, int slot, int reason) {
KeepaliveInfo ki = null;
try {
ki = mKeepalives.get(nai).get(slot);
@@ -621,7 +643,7 @@
if (ki == null) {
Log.e(TAG, "Event " + NetworkAgent.EVENT_SOCKET_KEEPALIVE + "," + slot + "," + reason
+ " for unknown keepalive " + slot + " on " + nai.toShortString());
- return;
+ return true;
}
// This can be called in a number of situations :
@@ -654,11 +676,13 @@
Log.w(TAG, "Discarded " + (ki.mResumed ? "onResumed" : "onStarted")
+ " callback for slot " + slot);
}
+ return true;
} else {
Log.d(TAG, "Failed to start keepalive " + slot + " on " + nai.toShortString()
+ ": " + reason);
// The message indicated some error trying to start: do call handleStopKeepalive.
handleStopKeepalive(nai, slot, reason);
+ return false;
}
} else if (KeepaliveInfo.STOPPING == ki.mStartedState) {
// The message indicated result of stopping : clean up keepalive slots.
@@ -666,9 +690,12 @@
+ " stopped: " + reason);
ki.mStartedState = KeepaliveInfo.NOT_STARTED;
cleanupStoppedKeepalive(nai, slot);
+ return true;
} else {
Log.wtf(TAG, "Event " + NetworkAgent.EVENT_SOCKET_KEEPALIVE + "," + slot + "," + reason
+ " for keepalive in wrong state: " + ki.toString());
+ // Although this is an unexpected event, the keepalive is not stopped here.
+ return true;
}
}
diff --git a/service/src/com/android/server/connectivity/ProxyTracker.java b/service/src/com/android/server/connectivity/ProxyTracker.java
index bc0929c..b3cbb2a 100644
--- a/service/src/com/android/server/connectivity/ProxyTracker.java
+++ b/service/src/com/android/server/connectivity/ProxyTracker.java
@@ -330,7 +330,7 @@
public void setDefaultProxy(@Nullable ProxyInfo proxyInfo) {
synchronized (mProxyLock) {
if (Objects.equals(mDefaultProxy, proxyInfo)) return;
- if (proxyInfo != null && !proxyInfo.isValid()) {
+ if (proxyInfo != null && !proxyInfo.isValid()) {
if (DBG) Log.d(TAG, "Invalid proxy properties, ignoring: " + proxyInfo);
return;
}
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 8e47235..7b5c298 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -31,7 +31,7 @@
// (currently, CTS 10, 11, and 12).
java_defaults {
name: "ConnectivityTestsLatestSdkDefaults",
- target_sdk_version: "33",
+ target_sdk_version: "34",
}
java_library {
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 a281aed..6b21dac 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
@@ -18,9 +18,7 @@
import static android.app.job.JobScheduler.RESULT_SUCCESS;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
-import static android.os.BatteryManager.BATTERY_PLUGGED_AC;
-import static android.os.BatteryManager.BATTERY_PLUGGED_USB;
-import static android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS;
+import static android.os.BatteryManager.BATTERY_PLUGGED_ANY;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.executeShellCommand;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.forceRunJob;
@@ -119,10 +117,6 @@
protected static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
protected static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
- // TODO: Update BatteryManager.BATTERY_PLUGGED_ANY as @TestApi
- public static final int BATTERY_PLUGGED_ANY =
- BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS;
-
private static final String NETWORK_STATUS_SEPARATOR = "\\|";
private static final int SECOND_IN_MS = 1000;
static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index af8938a..cf5fc50 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -172,6 +172,7 @@
// for modules other than Connectivity does not provide much value. Only run them in connectivity
// module MTS, so the tests only need to cover the case of an updated NetworkAgent.
@ConnectivityModuleTest
+@AppModeFull(reason = "Instant apps can't use NetworkAgent because it needs NETWORK_FACTORY'.")
class NetworkAgentTest {
private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
private val REMOTE_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.2")
@@ -982,13 +983,11 @@
.also { assertNotNull(agent.network?.bindSocket(it)) }
}
- @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackRegisterAndUnregister() {
validateQosCallbackRegisterAndUnregister(IPPROTO_TCP)
}
- @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackRegisterAndUnregisterWithDatagramSocket() {
validateQosCallbackRegisterAndUnregister(IPPROTO_UDP)
@@ -1025,13 +1024,11 @@
}
}
- @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackOnQosSession() {
validateQosCallbackOnQosSession(IPPROTO_TCP)
}
- @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackOnQosSessionWithDatagramSocket() {
validateQosCallbackOnQosSession(IPPROTO_UDP)
@@ -1090,7 +1087,6 @@
}
}
- @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackOnError() {
val (agent, qosTestSocket) = setupForQosSocket()
@@ -1129,7 +1125,6 @@
}
}
- @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackIdsAreMappedCorrectly() {
val (agent, qosTestSocket) = setupForQosSocket()
@@ -1170,7 +1165,6 @@
}
}
- @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackWhenNetworkReleased() {
val (agent, qosTestSocket) = setupForQosSocket()
@@ -1212,7 +1206,6 @@
)
}
- @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testUnregisterAfterReplacement() {
// Keeps an eye on all test networks.
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index d8a0b07..83b9b81 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -799,7 +799,7 @@
// harness, which is untagged, won't cause a failure.
long firstTotal = resultsWithTraffic.get(0).total;
for (QueryResult queryResult : resultsWithTraffic) {
- assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 10);
+ assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 12);
}
// Expect to see no traffic when querying for any tag in tagsWithNoTraffic or any
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 88b9baf..6c411cf 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -73,8 +73,8 @@
import android.system.OsConstants.IPPROTO_UDP
import android.system.OsConstants.SOCK_DGRAM
import android.util.Log
+import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.runner.AndroidJUnit4
import com.android.compatibility.common.util.PollingCheck
import com.android.compatibility.common.util.PropertyUtil
import com.android.modules.utils.build.SdkLevel.isAtLeastU
@@ -84,6 +84,8 @@
import com.android.networkstack.apishim.common.NsdShim
import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import com.android.testutils.TestableNetworkAgent
@@ -129,12 +131,15 @@
// tried sequentially
private const val REGISTRATION_TIMEOUT_MS = 10_000L
private const val DBG = false
+private const val TEST_PORT = 12345
private val nsdShim = NsdShimImpl.newInstance()
@AppModeFull(reason = "Socket cannot bind in instant app mode")
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
@ConnectivityModuleTest
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
class NsdManagerTest {
// Rule used to filter CtsNetTestCasesMaxTargetSdkXX
@get:Rule
@@ -436,6 +441,13 @@
return agent
}
+ private fun makeTestServiceInfo(network: Network? = null) = NsdServiceInfo().also {
+ it.serviceType = serviceType
+ it.serviceName = serviceName
+ it.network = network
+ it.port = TEST_PORT
+ }
+
@After
fun tearDown() {
if (TestUtils.shouldTestTApis()) {
@@ -536,8 +548,9 @@
assertTrue(resolvedService.attributes.containsKey("nullBinaryDataAttr"))
assertNull(resolvedService.attributes["nullBinaryDataAttr"])
assertTrue(resolvedService.attributes.containsKey("emptyBinaryDataAttr"))
- // TODO: change the check to target SDK U when this is what the code implements
- if (isAtLeastU()) {
+ if (isAtLeastU() || CompatChanges.isChangeEnabled(
+ ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND
+ )) {
assertArrayEquals(byteArrayOf(), resolvedService.attributes["emptyBinaryDataAttr"])
} else {
assertNull(resolvedService.attributes["emptyBinaryDataAttr"])
@@ -1048,6 +1061,52 @@
assertEquals(NsdManager.FAILURE_OPERATION_NOT_RUNNING, failedCb.errorCode)
}
+ @Test
+ fun testSubtypeAdvertisingAndDiscovery() {
+ val si = makeTestServiceInfo(network = testNetwork1.network)
+ // Test "_type._tcp.local,_subtype" syntax with the registration
+ si.serviceType = si.serviceType + ",_subtype"
+
+ val registrationRecord = NsdRegistrationRecord()
+
+ val baseTypeDiscoveryRecord = NsdDiscoveryRecord()
+ val subtypeDiscoveryRecord = NsdDiscoveryRecord()
+ val otherSubtypeDiscoveryRecord = NsdDiscoveryRecord()
+ tryTest {
+ registerService(registrationRecord, si)
+
+ // Test "_subtype._type._tcp.local" syntax with discovery. Note this is not
+ // "_subtype._sub._type._tcp.local".
+ nsdManager.discoverServices(serviceType,
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, Executor { it.run() }, baseTypeDiscoveryRecord)
+ nsdManager.discoverServices("_othersubtype.$serviceType",
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, Executor { it.run() }, otherSubtypeDiscoveryRecord)
+ nsdManager.discoverServices("_subtype.$serviceType",
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, Executor { it.run() }, subtypeDiscoveryRecord)
+
+ subtypeDiscoveryRecord.waitForServiceDiscovered(
+ serviceName, serviceType, testNetwork1.network)
+ baseTypeDiscoveryRecord.waitForServiceDiscovered(
+ serviceName, serviceType, testNetwork1.network)
+ otherSubtypeDiscoveryRecord.expectCallback<DiscoveryStarted>()
+ // The subtype callback was registered later but called, no need for an extra delay
+ otherSubtypeDiscoveryRecord.assertNoCallback(timeoutMs = 0)
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(baseTypeDiscoveryRecord)
+ nsdManager.stopServiceDiscovery(subtypeDiscoveryRecord)
+ nsdManager.stopServiceDiscovery(otherSubtypeDiscoveryRecord)
+
+ baseTypeDiscoveryRecord.expectCallback<DiscoveryStopped>()
+ subtypeDiscoveryRecord.expectCallback<DiscoveryStopped>()
+ otherSubtypeDiscoveryRecord.expectCallback<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord)
+ }
+ }
+
/**
* Register a service and return its registration record.
*/
diff --git a/tests/cts/tethering/AndroidTestTemplate.xml b/tests/cts/tethering/AndroidTestTemplate.xml
index 9b33afe..dd5b23e 100644
--- a/tests/cts/tethering/AndroidTestTemplate.xml
+++ b/tests/cts/tethering/AndroidTestTemplate.xml
@@ -20,6 +20,7 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index f05cebe..522ac8c 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -167,6 +167,7 @@
import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.FunctionalUtils.ignoreExceptions;
+import static com.android.testutils.HandlerUtils.visibleOnHandlerThread;
import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor;
import static com.android.testutils.MiscAsserts.assertContainsAll;
import static com.android.testutils.MiscAsserts.assertContainsExactly;
@@ -376,7 +377,6 @@
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.ArrayTrackRecord;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.LocationPermissionChecker;
@@ -541,8 +541,7 @@
private static final int TEST_PACKAGE_UID2 = 321;
private static final int TEST_PACKAGE_UID3 = 456;
- private static final int PACKET_WAKEUP_MASK = 0xffff0000;
- private static final int PACKET_WAKEUP_MARK = 0x88880000;
+ private static final int PACKET_WAKEUP_MARK_MASK = 0x80000000;
private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn";
@@ -869,7 +868,7 @@
@Override
public void sendStickyBroadcast(Intent intent, Bundle options) {
// Verify that delivery group policy APIs were used on U.
- if (SdkLevel.isAtLeastU() && CONNECTIVITY_ACTION.equals(intent.getAction())) {
+ if (mDeps.isAtLeastU() && CONNECTIVITY_ACTION.equals(intent.getAction())) {
final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO,
NetworkInfo.class);
try {
@@ -1085,7 +1084,7 @@
}
private void onValidationRequested() throws Exception {
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
verify(mNetworkMonitor).notifyNetworkConnectedParcel(any());
} else {
verify(mNetworkMonitor).notifyNetworkConnected(any(), any());
@@ -1924,14 +1923,15 @@
doReturn(0).when(mResources).getInteger(R.integer.config_activelyPreferBadWifi);
doReturn(true).when(mResources)
.getBoolean(R.bool.config_cellular_radio_timesharing_capable);
- doReturn(PACKET_WAKEUP_MASK).when(mResources).getInteger(
+ doReturn(PACKET_WAKEUP_MARK_MASK).when(mResources).getInteger(
R.integer.config_networkWakeupPacketMask);
- doReturn(PACKET_WAKEUP_MARK).when(mResources).getInteger(
+ doReturn(PACKET_WAKEUP_MARK_MASK).when(mResources).getInteger(
R.integer.config_networkWakeupPacketMark);
}
class ConnectivityServiceDependencies extends ConnectivityService.Dependencies {
final ConnectivityResources mConnRes;
+ final ArraySet<Pair<Long, Integer>> mEnabledChangeIds = new ArraySet<>();
ConnectivityServiceDependencies(final Context mockResContext) {
mConnRes = new ConnectivityResources(mockResContext);
@@ -1997,7 +1997,7 @@
@Override
public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
@NonNull final Context context, @NonNull final TelephonyManager tm) {
- return SdkLevel.isAtLeastT() ? mCarrierPrivilegeAuthenticator : null;
+ return mDeps.isAtLeastT() ? mCarrierPrivilegeAuthenticator : null;
}
@Override
@@ -2096,6 +2096,61 @@
}
}
+ public void setChangeIdEnabled(final boolean enabled, final long changeId, final int uid) {
+ final Pair<Long, Integer> data = new Pair<>(changeId, uid);
+ // mEnabledChangeIds is read on the handler thread and maybe the test thread, so
+ // make sure both threads see it before continuing.
+ visibleOnHandlerThread(mCsHandlerThread.getThreadHandler(), () -> {
+ if (enabled) {
+ mEnabledChangeIds.add(data);
+ } else {
+ mEnabledChangeIds.remove(data);
+ }
+ });
+ }
+
+ @Override
+ public boolean isChangeEnabled(final long changeId, final int uid) {
+ return mEnabledChangeIds.contains(new Pair<>(changeId, uid));
+ }
+
+ // In AOSP, build version codes are all over the place (e.g. at the time of this writing
+ // U == V). Define custom ones.
+ private static final int VERSION_UNMOCKED = -1;
+ private static final int VERSION_R = 1;
+ private static final int VERSION_S = 2;
+ private static final int VERSION_T = 3;
+ private static final int VERSION_U = 4;
+ private static final int VERSION_V = 5;
+ private static final int VERSION_MAX = VERSION_V;
+ private int mSdkLevel = VERSION_UNMOCKED;
+
+ private void setBuildSdk(final int sdkLevel) {
+ if (sdkLevel > VERSION_MAX) {
+ throw new IllegalArgumentException("setBuildSdk must not be called with"
+ + " Build.VERSION constants but Dependencies.VERSION_* constants");
+ }
+ visibleOnHandlerThread(mCsHandlerThread.getThreadHandler(), () -> mSdkLevel = sdkLevel);
+ }
+
+ @Override
+ public boolean isAtLeastS() {
+ return mSdkLevel == VERSION_UNMOCKED ? super.isAtLeastS()
+ : mSdkLevel >= VERSION_S;
+ }
+
+ @Override
+ public boolean isAtLeastT() {
+ return mSdkLevel == VERSION_UNMOCKED ? super.isAtLeastT()
+ : mSdkLevel >= VERSION_T;
+ }
+
+ @Override
+ public boolean isAtLeastU() {
+ return mSdkLevel == VERSION_UNMOCKED ? super.isAtLeastU()
+ : mSdkLevel >= VERSION_U;
+ }
+
@Override
public BpfNetMaps getBpfNetMaps(Context context, INetd netd) {
return mBpfNetMaps;
@@ -2188,6 +2243,15 @@
// Call mocked destroyLiveTcpSocketsByOwnerUids so that test can verify this method call
mDestroySocketsWrapper.destroyLiveTcpSocketsByOwnerUids(ownerUids);
}
+
+ final ArrayTrackRecord<Long>.ReadHead mScheduledEvaluationTimeouts =
+ new ArrayTrackRecord<Long>().newReadHead();
+ @Override
+ public void scheduleEvaluationTimeout(@NonNull Handler handler,
+ @NonNull final Network network, final long delayMs) {
+ mScheduledEvaluationTimeouts.add(delayMs);
+ super.scheduleEvaluationTimeout(handler, network, delayMs);
+ }
}
private class AutomaticOnOffKeepaliveTrackerDependencies
@@ -5924,7 +5988,7 @@
doReturn(0).when(mResources).getInteger(R.integer.config_activelyPreferBadWifi);
mPolicyTracker.reevaluate();
waitForIdle();
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
// U+ ignore the setting and always actively prefers bad wifi
assertTrue(mService.mNetworkRanker.getConfiguration().activelyPreferBadWifi());
} else {
@@ -6052,10 +6116,13 @@
wifiCallback.assertNoCallback();
}
- public void doTestPreferBadWifi(final boolean preferBadWifi) throws Exception {
+ public void doTestPreferBadWifi(final boolean avoidBadWifi,
+ final boolean preferBadWifi,
+ @NonNull Predicate<Long> checkUnvalidationTimeout) throws Exception {
// Pretend we're on a carrier that restricts switching away from bad wifi, and
// depending on the parameter one that may indeed prefer bad wifi.
- doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
+ doReturn(avoidBadWifi ? 1 : 0).when(mResources)
+ .getInteger(R.integer.config_networkAvoidBadWifi);
doReturn(preferBadWifi ? 1 : 0).when(mResources)
.getInteger(R.integer.config_activelyPreferBadWifi);
mPolicyTracker.reevaluate();
@@ -6077,7 +6144,9 @@
mWiFiAgent.connect(false);
wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
- if (preferBadWifi) {
+ mDeps.mScheduledEvaluationTimeouts.poll(TIMEOUT_MS, t -> checkUnvalidationTimeout.test(t));
+
+ if (!avoidBadWifi && preferBadWifi) {
expectUnvalidationCheckWillNotify(mWiFiAgent, NotificationType.LOST_INTERNET);
mDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
} else {
@@ -6087,15 +6156,31 @@
}
@Test
- public void testPreferBadWifi_doNotPrefer() throws Exception {
+ public void testPreferBadWifi_doNotAvoid_doNotPrefer() throws Exception {
// Starting with U this mode is no longer supported and can't actually be tested
- assumeFalse(SdkLevel.isAtLeastU());
- doTestPreferBadWifi(false /* preferBadWifi */);
+ assumeFalse(mDeps.isAtLeastU());
+ doTestPreferBadWifi(false /* avoidBadWifi */, false /* preferBadWifi */,
+ timeout -> timeout < 14_000);
}
@Test
- public void testPreferBadWifi_doPrefer() throws Exception {
- doTestPreferBadWifi(true /* preferBadWifi */);
+ public void testPreferBadWifi_doNotAvoid_doPrefer() throws Exception {
+ doTestPreferBadWifi(false /* avoidBadWifi */, true /* preferBadWifi */,
+ timeout -> timeout > 14_000);
+ }
+
+ @Test
+ public void testPreferBadWifi_doAvoid_doNotPrefer() throws Exception {
+ // If avoidBadWifi=true, then preferBadWifi should be irrelevant. Test anyway.
+ doTestPreferBadWifi(true /* avoidBadWifi */, false /* preferBadWifi */,
+ timeout -> timeout < 14_000);
+ }
+
+ @Test
+ public void testPreferBadWifi_doAvoid_doPrefer() throws Exception {
+ // If avoidBadWifi=true, then preferBadWifi should be irrelevant. Test anyway.
+ doTestPreferBadWifi(true /* avoidBadWifi */, true /* preferBadWifi */,
+ timeout -> timeout < 14_000);
}
@Test
@@ -9742,7 +9827,7 @@
final Set<Integer> excludedUids = new ArraySet<Integer>();
excludedUids.add(VPN_UID);
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
// On T onwards, the corresponding SDK sandbox UID should also be excluded
excludedUids.add(toSdkSandboxUid(VPN_UID));
}
@@ -9790,7 +9875,7 @@
vpnDefaultCallbackAsUid.assertNoCallback();
excludedUids.add(uid);
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
// On T onwards, the corresponding SDK sandbox UID should also be excluded
excludedUids.add(toSdkSandboxUid(uid));
}
@@ -10524,7 +10609,7 @@
private void verifyClatdStart(@Nullable InOrder inOrder, @NonNull String iface, int netId,
@NonNull String nat64Prefix) throws Exception {
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
verifyWithOrder(inOrder, mClatCoordinator)
.clatStart(eq(iface), eq(netId), eq(new IpPrefix(nat64Prefix)));
} else {
@@ -10534,7 +10619,7 @@
private void verifyNeverClatdStart(@Nullable InOrder inOrder, @NonNull String iface)
throws Exception {
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
verifyNeverWithOrder(inOrder, mClatCoordinator).clatStart(eq(iface), anyInt(), any());
} else {
verifyNeverWithOrder(inOrder, mMockNetd).clatdStart(eq(iface), anyString());
@@ -10543,7 +10628,7 @@
private void verifyClatdStop(@Nullable InOrder inOrder, @NonNull String iface)
throws Exception {
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
verifyWithOrder(inOrder, mClatCoordinator).clatStop();
} else {
verifyWithOrder(inOrder, mMockNetd).clatdStop(eq(iface));
@@ -10552,7 +10637,7 @@
private void verifyNeverClatdStop(@Nullable InOrder inOrder, @NonNull String iface)
throws Exception {
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
verifyNeverWithOrder(inOrder, mClatCoordinator).clatStop();
} else {
verifyNeverWithOrder(inOrder, mMockNetd).clatdStop(eq(iface));
@@ -10745,7 +10830,7 @@
networkCallback.assertNoCallback();
verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
verifyWakeupModifyInterface(CLAT_MOBILE_IFNAME, false);
}
@@ -10785,7 +10870,7 @@
assertRoutesAdded(cellNetId, stackedDefault);
verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
verifyWakeupModifyInterface(CLAT_MOBILE_IFNAME, true);
}
@@ -10804,7 +10889,7 @@
verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);
verify(mMockNetd, times(1)).interfaceGetCfg(CLAT_MOBILE_IFNAME);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
verifyWakeupModifyInterface(CLAT_MOBILE_IFNAME, false);
}
@@ -10815,13 +10900,13 @@
verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(Integer.toString(TRANSPORT_CELLULAR)));
verify(mMockNetd).networkDestroy(cellNetId);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
verify(mMockNetd).setNetworkAllowlist(any());
} else {
verify(mMockNetd, never()).setNetworkAllowlist(any());
}
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
verifyWakeupModifyInterface(MOBILE_IFNAME, false);
}
@@ -10855,7 +10940,7 @@
// assertRoutesAdded sees all calls since last mMockNetd reset, so expect IPv6 routes again.
assertRoutesAdded(cellNetId, ipv6Subnet, ipv6Default, stackedDefault);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
verifyWakeupModifyInterface(MOBILE_IFNAME, true);
}
@@ -10868,20 +10953,20 @@
networkCallback.assertNoCallback();
verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
verifyWakeupModifyInterface(CLAT_MOBILE_IFNAME, false);
}
verify(mMockNetd).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
eq(Integer.toString(TRANSPORT_CELLULAR)));
verify(mMockNetd).networkDestroy(cellNetId);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
verify(mMockNetd).setNetworkAllowlist(any());
} else {
verify(mMockNetd, never()).setNetworkAllowlist(any());
}
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
verifyWakeupModifyInterface(MOBILE_IFNAME, false);
}
@@ -11246,6 +11331,212 @@
assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
}
+ /*
+ * Note for maintainers about how PAC proxies are implemented in Android.
+ *
+ * Generally, a proxy is just a hostname and a port to which requests are sent, instead of
+ * sending them directly. Most HTTP libraries know to use this protocol, and the Java
+ * language has properties to help handling these :
+ * https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html
+ * Unfortunately these properties are very old and do not take multi-networking into account.
+ *
+ * A PAC proxy consists of a javascript file stored on a server, and the client is expected to
+ * download the file and interpret the javascript for each HTTP request to know where to direct
+ * it. The device must therefore run code (namely, a javascript interpreter) to interpret the
+ * PAC file correctly. Most HTTP libraries do not know how to do this, since they do not
+ * embark a javascript interpreter (and it would be generally unreasonable for them to do
+ * so). Some apps, notably browsers, do know how to do this, but they are the exception rather
+ * than the rule.
+ * So to support most apps, Android embarks the javascript interpreter. When a network is
+ * configured to have a PAC proxy, Android will first set the ProxyInfo object to an object
+ * that contains the PAC URL (to communicate that to apps that know how to use it), then
+ * download the PAC file and start a native process which will open a server on localhost,
+ * and uses the interpreter inside WebView to interpret the PAC file. This server takes
+ * a little bit of time to start and will listen on a random port. When the port is known,
+ * the framework receives a notification and it updates the ProxyInfo in LinkProperties
+ * as well as send a broadcast to inform apps. This new ProxyInfo still contains the PAC URL,
+ * but it also contains "localhost" as the host and the port that the server listens to as
+ * the port. This will let HTTP libraries that don't have a javascript interpreter work,
+ * because they'll send the requests to this server running on localhost on the correct port,
+ * and this server will do what is appropriate with each request according to the PAC file.
+ *
+ * Note that at the time of this writing, Android does not support starting multiple local
+ * PAC servers, though this would be possible in theory by just starting multiple instances
+ * of this process running their server on different ports. As a stopgap measure, Android
+ * keeps one local server which is always the one for the default network. If a non-default
+ * network has a PAC proxy, it will have a LinkProperties with a port of -1, which means it
+ * could still be used by apps that know how to use the PAC URL directly, but not by HTTP
+ * libs that don't know how to do that. When a network with a PAC proxy becomes the default,
+ * the local server is started. When a network without a PAC proxy becomes the default, the
+ * local server is stopped if it is running (and the LP for the old default network should
+ * be reset to have a port of -1).
+ *
+ * In principle, each network can be configured with a different proxy (typically in the
+ * network settings for a Wi-Fi network). These end up exposed in the LinkProperties of the
+ * relevant network.
+ * Android also exposes ConnectivityManager#getDefaultProxy, which is simply the proxy for
+ * the default network. This was retrofitted from a time where Android did not support multiple
+ * concurrent networks, hence the difficult architecture.
+ * Note that there is also a "global" proxy that can be set by the DPM. When this is set,
+ * it overrides the proxy settings for every single network at the same time – that is, the
+ * system behaves as if the global proxy is the configured proxy for each network.
+ *
+ * Generally there are four ways for apps to look up the proxy.
+ * - Looking up the ProxyInfo in the LinkProperties for a network.
+ * - Listening to the PROXY_CHANGED_ACTION broadcast
+ * - Calling ConnectivityManager#getDefaultProxy, or ConnectivityManager#getProxyForNetwork
+ * which can be used to retrieve the proxy for any given network or the default network by
+ * passing null.
+ * - Reading the standard JVM properties (http{s,}.proxy{Host,Port}). See the Java
+ * distribution documentation for details on how these are supposed to work :
+ * https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html
+ * In Android, these are set by ActivityThread in each process in response to the broadcast.
+ * Many apps actually use these, and it's important they work because it's part of the
+ * Java standard, meaning they need to be set for existing Java libs to work on Android.
+ */
+ @Test
+ public void testPacProxy() throws Exception {
+ final Uri pacUrl = Uri.parse("https://pac-url");
+ mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellAgent.connect(true);
+
+ final TestNetworkCallback wifiCallback = new TestNetworkCallback();
+ final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).build();
+ mCm.registerNetworkCallback(wifiRequest, wifiCallback);
+
+ mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiAgent.connect(true);
+ wifiCallback.expectAvailableThenValidatedCallbacks(mWiFiAgent);
+
+ final ProxyInfo initialProxyInfo = ProxyInfo.buildPacProxy(pacUrl);
+ final LinkProperties testLinkProperties = new LinkProperties();
+ testLinkProperties.setHttpProxy(initialProxyInfo);
+ mWiFiAgent.sendLinkProperties(testLinkProperties);
+ wifiCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mWiFiAgent);
+
+ // At first the local PAC proxy server is unstarted (see the description of what the local
+ // server is in the comment at the top of this method). It will contain the PAC URL and a
+ // port of -1 because it is unstarted. Check that all ways of getting that proxy info
+ // returns the same object that was initially created.
+ final ProxyInfo unstartedDefaultProxyInfo = mService.getProxyForNetwork(null);
+ final ProxyInfo unstartedWifiProxyInfo = mService.getProxyForNetwork(
+ mWiFiAgent.getNetwork());
+ final LinkProperties unstartedLp =
+ mService.getLinkProperties(mWiFiAgent.getNetwork());
+
+ assertEquals(initialProxyInfo, unstartedDefaultProxyInfo);
+ assertEquals(initialProxyInfo, unstartedWifiProxyInfo);
+ assertEquals(initialProxyInfo, unstartedLp.getHttpProxy());
+
+ // Make sure the cell network is unaffected. The LP are per-network and each network has
+ // its own configuration. The default proxy and broadcast are system-wide, and the JVM
+ // properties are per-process, and therefore can have only one value at any given time,
+ // so the code sets them to the proxy of the default network (TODO : really, since the
+ // default process is per-network, the JVM properties (http.proxyHost family – see
+ // the comment at the top of the method for details about these) also should be per-network
+ // and even the broadcast contents should be but none of this is implemented). The LP are
+ // still per-network, and a process that wants to use a non-default network is supposed to
+ // look up the proxy in its LP and it has to be correct.
+ assertNull(mService.getLinkProperties(mCellAgent.getNetwork()).getHttpProxy());
+ assertNull(mService.getProxyForNetwork(mCellAgent.getNetwork()));
+
+ // Simulate PacManager sending the notification that the local server has started
+ final ProxyInfo servingProxyInfo = new ProxyInfo(pacUrl, 2097);
+ final ExpectedBroadcast b1 = expectProxyChangeAction(servingProxyInfo);
+ mService.simulateUpdateProxyInfo(mWiFiAgent.getNetwork(), servingProxyInfo);
+// wifiCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mWiFiAgent);
+ b1.expectBroadcast();
+
+ final ProxyInfo startedDefaultProxyInfo = mService.getProxyForNetwork(null);
+ final ProxyInfo startedWifiProxyInfo = mService.getProxyForNetwork(
+ mWiFiAgent.getNetwork());
+ final LinkProperties startedLp = mService.getLinkProperties(mWiFiAgent.getNetwork());
+ // TODO : activate these tests when b/138810051 is fixed.
+// assertEquals(servingProxyInfo, startedDefaultProxyInfo);
+// assertEquals(servingProxyInfo, startedWifiProxyInfo);
+// assertEquals(servingProxyInfo, startedLp.getHttpProxy());
+// // Make sure the cell network is still unaffected
+// assertNull(mService.getLinkProperties(mCellAgent.getNetwork()).getHttpProxy());
+// assertNull(mService.getProxyForNetwork(mCellAgent.getNetwork()));
+//
+// final Uri ethPacUrl = Uri.parse("https://ethernet-pac-url");
+// final TestableNetworkCallback ethernetCallback = new TestableNetworkCallback();
+// final NetworkRequest ethernetRequest = new NetworkRequest.Builder()
+// .addTransportType(TRANSPORT_ETHERNET).build();
+// mEthernetAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
+// mCm.registerNetworkCallback(ethernetRequest, ethernetCallback);
+// mEthernetAgent.connect(true);
+// ethernetCallback.expectAvailableThenValidatedCallbacks(mEthernetAgent);
+// // Wifi is no longer the default, so it should get a callback to LP changed with a PAC
+// // proxy but a port of -1 (since the local proxy doesn't serve wifi now)
+// wifiCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+// lp -> lp.getLp().getHttpProxy().getPort() == -1
+// && lp.getLp().getHttpProxy().isPacProxy());
+// wifiCallback.expect(CallbackEntry.LOSING, mWiFiAgent);
+//
+// final ProxyInfo ethProxy = ProxyInfo.buildPacProxy(ethPacUrl);
+// final LinkProperties ethLinkProperties = new LinkProperties();
+// ethLinkProperties.setHttpProxy(ethProxy);
+// mEthernetAgent.sendLinkProperties(ethLinkProperties);
+// ethernetCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mEthernetAgent);
+// // Default network is Ethernet
+// assertEquals(ethProxy, mService.getProxyForNetwork(null));
+// assertEquals(ethProxy, mService.getProxyForNetwork(mEthernetAgent.getNetwork()));
+// // Proxy info for WiFi ideally should be the old one with the old server still running,
+// // but as the PAC code only handles one server at any given time, this can't work. Wifi
+// // having null as a proxy also won't work (apps using WiFi will try to access the network
+// // without proxy) but is not as bad as having the new proxy (that would send requests
+// // over the default network).
+// assertEquals(initialProxyInfo, mService.getProxyForNetwork(mWiFiAgent.getNetwork()));
+// assertNull(mService.getProxyForNetwork(mCellAgent.getNetwork()));
+//
+// final ProxyInfo servingEthProxy = new ProxyInfo(pacUrl, 2099);
+// final ExpectedBroadcast b2 = expectProxyChangeAction(servingEthProxy);
+// final ExpectedBroadcast b3 = expectProxyChangeAction(servingProxyInfo);
+//
+// mService.simulateUpdateProxyInfo(mEthernetAgent.getNetwork(), servingEthProxy);
+// ethernetCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mEthernetAgent);
+// assertEquals(servingEthProxy, mService.getProxyForNetwork(null));
+// assertEquals(servingEthProxy, mService.getProxyForNetwork(
+// mEthernetAgent.getNetwork()));
+// assertEquals(initialProxyInfo,
+// mService.getProxyForNetwork(mWiFiAgent.getNetwork()));
+// assertNull(mService.getProxyForNetwork(mCellAgent.getNetwork()));
+// b2.expectBroadcast();
+//
+// // Ethernet disconnects, back to WiFi
+// mEthernetAgent.disconnect();
+// ethernetCallback.expect(CallbackEntry.LOST, mEthernetAgent);
+// wifiCallback.assertNoCallback();
+//
+// assertEquals(initialProxyInfo, mService.getProxyForNetwork(null));
+// assertEquals(initialProxyInfo,
+// mService.getProxyForNetwork(mWiFiAgent.getNetwork()));
+// assertNull(mService.getProxyForNetwork(mCellAgent.getNetwork()));
+//
+// // After a while the PAC file for wifi is resolved again
+// mService.simulateUpdateProxyInfo(mWiFiAgent.getNetwork(), servingProxyInfo);
+// wifiCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mWiFiAgent);
+// assertEquals(servingProxyInfo, mService.getProxyForNetwork(null));
+// assertEquals(servingProxyInfo,
+// mService.getProxyForNetwork(mWiFiAgent.getNetwork()));
+// assertNull(mService.getProxyForNetwork(mCellAgent.getNetwork()));
+// b3.expectBroadcast();
+//
+// // Expect a broadcast after wifi disconnected. The proxy might be default proxy or an
+// // empty proxy built by buildDirectProxy. See {@link ProxyTracker.sendProxyBroadcast}.
+// // Thus here expects a broadcast will be received but does not verify the content of the
+// // proxy.
+// final ExpectedBroadcast b4 = expectProxyChangeAction();
+// mWiFiAgent.disconnect();
+// b4.expectBroadcast();
+// wifiCallback.expect(CallbackEntry.LOST, mWiFiAgent);
+// assertNull(mService.getProxyForNetwork(null));
+ assertNull(mService.getLinkProperties(mCellAgent.getNetwork()).getHttpProxy());
+ assertNull(mService.getGlobalProxy());
+ }
+
@Test
public void testGetProxyForVPN() throws Exception {
final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
@@ -11322,7 +11613,7 @@
mMockVpn.establish(lp, uid, vpnRange);
assertVpnUidRangesUpdated(true, vpnRange, uid);
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
// On T and above, VPN should have rules for 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
@@ -12311,7 +12602,7 @@
@Test
public void testUnderlyingNetworksWillBeSetInNetworkAgentInfoConstructor() throws Exception {
- assumeTrue(SdkLevel.isAtLeastT());
+ assumeTrue(mDeps.isAtLeastT());
final Network network1 = new Network(100);
final Network network2 = new Network(101);
final List<Network> underlyingNetworks = new ArrayList<>();
@@ -12639,9 +12930,16 @@
throws Exception {
InOrder inOrder = inOrder(mMockNetd, mDestroySocketsWrapper);
final Set<Integer> exemptUidSet = new ArraySet<>(List.of(exemptUid, Process.VPN_UID));
+ ArgumentCaptor<int[]> exemptUidCaptor = ArgumentCaptor.forClass(int[].class);
- inOrder.verify(mDestroySocketsWrapper).destroyLiveTcpSockets(
- UidRange.toIntRanges(vpnRanges), exemptUidSet);
+ if (mDeps.isAtLeastU()) {
+ inOrder.verify(mDestroySocketsWrapper).destroyLiveTcpSockets(
+ UidRange.toIntRanges(vpnRanges), exemptUidSet);
+ } else {
+ inOrder.verify(mMockNetd).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)),
+ exemptUidCaptor.capture());
+ assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
+ }
if (add) {
inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(
@@ -12653,8 +12951,14 @@
toUidRangeStableParcels(vpnRanges), PREFERENCE_ORDER_VPN));
}
- inOrder.verify(mDestroySocketsWrapper).destroyLiveTcpSockets(
- UidRange.toIntRanges(vpnRanges), exemptUidSet);
+ if (mDeps.isAtLeastU()) {
+ inOrder.verify(mDestroySocketsWrapper).destroyLiveTcpSockets(
+ UidRange.toIntRanges(vpnRanges), exemptUidSet);
+ } else {
+ inOrder.verify(mMockNetd).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)),
+ exemptUidCaptor.capture());
+ assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
+ }
}
@Test
@@ -16204,7 +16508,7 @@
mCellAgent.getNetwork().netId,
toUidRangeStableParcels(allowedRanges),
0 /* subPriority */);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[]{config1User});
} else {
inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
@@ -16222,7 +16526,7 @@
mCellAgent.getNetwork().netId,
toUidRangeStableParcels(allowedRanges),
0 /* subPriority */);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[]{config2Users});
} else {
inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
@@ -16253,7 +16557,7 @@
mCellAgent.getNetwork().netId,
allowAllUidRangesParcel,
0 /* subPriority */);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
inOrder.verify(mMockNetd).setNetworkAllowlist(
new NativeUidRangeConfig[]{cellAllAllowedConfig});
} else {
@@ -16272,7 +16576,7 @@
// making the order of the list undeterministic. Thus, verify this in order insensitive way.
final ArgumentCaptor<NativeUidRangeConfig[]> configsCaptor = ArgumentCaptor.forClass(
NativeUidRangeConfig[].class);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
inOrder.verify(mMockNetd).setNetworkAllowlist(configsCaptor.capture());
assertContainsAll(List.of(configsCaptor.getValue()),
List.of(cellAllAllowedConfig, enterpriseAllAllowedConfig));
@@ -16306,7 +16610,7 @@
mCellAgent.getNetwork().netId,
excludeAppRangesParcel,
0 /* subPriority */);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
inOrder.verify(mMockNetd).setNetworkAllowlist(configsCaptor.capture());
assertContainsAll(List.of(configsCaptor.getValue()),
List.of(cellExcludeAppConfig, enterpriseAllAllowedConfig));
@@ -16318,7 +16622,7 @@
mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
r -> r.run(), listener);
listener.expectOnComplete();
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
inOrder.verify(mMockNetd).setNetworkAllowlist(configsCaptor.capture());
assertContainsAll(List.of(configsCaptor.getValue()),
List.of(cellAllAllowedConfig, enterpriseAllAllowedConfig));
@@ -16330,7 +16634,7 @@
// disconnects.
enterpriseAgent.disconnect();
waitForIdle();
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
inOrder.verify(mMockNetd).setNetworkAllowlist(
new NativeUidRangeConfig[]{cellAllAllowedConfig});
} else {
@@ -16355,7 +16659,7 @@
List.of(prefBuilder.build()),
r -> r.run(), listener);
listener.expectOnComplete();
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[]{});
} else {
inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
@@ -16383,7 +16687,7 @@
mCellAgent.getNetwork().netId,
excludeAppRangesParcel,
0 /* subPriority */);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
inOrder.verify(mMockNetd).setNetworkAllowlist(
new NativeUidRangeConfig[]{cellExcludeAppConfig});
} else {
@@ -16407,7 +16711,7 @@
// making the order of the list undeterministic. Thus, verify this in order insensitive way.
final ArgumentCaptor<NativeUidRangeConfig[]> configsCaptor = ArgumentCaptor.forClass(
NativeUidRangeConfig[].class);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
inOrder.verify(mMockNetd).setNetworkAllowlist(configsCaptor.capture());
assertContainsAll(List.of(configsCaptor.getValue()),
List.of(enterpriseAllAllowedConfig, cellExcludeAppConfig));
@@ -16418,7 +16722,7 @@
// Verify issuing with cellular set only when enterprise network disconnects.
enterpriseAgent.disconnect();
waitForIdle();
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
inOrder.verify(mMockNetd).setNetworkAllowlist(
new NativeUidRangeConfig[]{cellExcludeAppConfig});
} else {
@@ -16427,7 +16731,7 @@
mCellAgent.disconnect();
waitForIdle();
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[]{});
} else {
inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
@@ -16483,7 +16787,7 @@
profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
final TestOnCompleteListener listener = new TestOnCompleteListener();
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
mCm.setProfileNetworkPreferences(testHandle,
List.of(profileNetworkPreferenceBuilder.build()),
r -> r.run(), listener);
@@ -16591,7 +16895,7 @@
agent.getNetwork().getNetId(),
intToUidRangeStableParcels(uids),
preferenceOrder);
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids200Parcel);
}
@@ -16599,7 +16903,7 @@
uids.add(400);
nc.setAllowedUids(uids);
agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
cb.expectCaps(agent, c -> c.getAllowedUids().equals(uids));
} else {
cb.assertNoCallback();
@@ -16610,13 +16914,13 @@
agent.getNetwork().getNetId(),
intToUidRangeStableParcels(uids),
preferenceOrder);
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids300400Parcel);
}
nc.setAllowedUids(uids);
agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
cb.expectCaps(agent, c -> c.getAllowedUids().equals(uids));
inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids200Parcel);
} else {
@@ -16627,7 +16931,7 @@
uids.add(600);
nc.setAllowedUids(uids);
agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
cb.expectCaps(agent, c -> c.getAllowedUids().equals(uids));
} else {
cb.assertNoCallback();
@@ -16636,7 +16940,7 @@
agent.getNetwork().getNetId(),
intToUidRangeStableParcels(uids),
preferenceOrder);
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids600Parcel);
inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids300400Parcel);
}
@@ -16644,7 +16948,7 @@
uids.clear();
nc.setAllowedUids(uids);
agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
cb.expectCaps(agent, c -> c.getAllowedUids().isEmpty());
inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids600Parcel);
} else {
@@ -16697,7 +17001,7 @@
// Cell gets to set the service UID as access UID
ncb.setAllowedUids(serviceUidSet);
mEthernetAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
- if (SdkLevel.isAtLeastT() && hasAutomotiveFeature) {
+ if (mDeps.isAtLeastT() && hasAutomotiveFeature) {
cb.expectCaps(mEthernetAgent, c -> c.getAllowedUids().equals(serviceUidSet));
} else {
// S and no automotive feature must ignore access UIDs.
@@ -16750,7 +17054,7 @@
cb.expectAvailableThenValidatedCallbacks(mCellAgent);
ncb.setAllowedUids(serviceUidSet);
mCellAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
cb.expectCaps(mCellAgent, c -> c.getAllowedUids().equals(serviceUidSet));
} else {
// S must ignore access UIDs.
@@ -16760,7 +17064,7 @@
// ...but not to some other UID. Rejection sets UIDs to the empty set
ncb.setAllowedUids(nonServiceUidSet);
mCellAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
- if (SdkLevel.isAtLeastT()) {
+ if (mDeps.isAtLeastT()) {
cb.expectCaps(mCellAgent, c -> c.getAllowedUids().isEmpty());
} else {
// S must ignore access UIDs.
@@ -17682,14 +17986,14 @@
@Test
public void testIgnoreValidationAfterRoamEnabled() throws Exception {
- final boolean enabled = !SdkLevel.isAtLeastT();
+ final boolean enabled = !mDeps.isAtLeastT();
doTestIgnoreValidationAfterRoam(5_000, enabled);
}
@Test
public void testShouldIgnoreValidationFailureAfterRoam() {
// Always disabled on T+.
- assumeFalse(SdkLevel.isAtLeastT());
+ assumeFalse(mDeps.isAtLeastT());
NetworkAgentInfo nai = fakeWifiNai(new NetworkCapabilities());
@@ -17932,8 +18236,8 @@
final String expectedPrefix = makeNflogPrefix(WIFI_IFNAME,
mWiFiAgent.getNetwork().getNetworkHandle());
- verify(mMockNetd).wakeupAddInterface(WIFI_IFNAME, expectedPrefix, PACKET_WAKEUP_MARK,
- PACKET_WAKEUP_MASK);
+ verify(mMockNetd).wakeupAddInterface(WIFI_IFNAME, expectedPrefix, PACKET_WAKEUP_MARK_MASK,
+ PACKET_WAKEUP_MARK_MASK);
}
@Test
@@ -17943,11 +18247,11 @@
mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
mCellAgent.connect(false /* validated */);
- if (SdkLevel.isAtLeastU()) {
+ if (mDeps.isAtLeastU()) {
final String expectedPrefix = makeNflogPrefix(MOBILE_IFNAME,
mCellAgent.getNetwork().getNetworkHandle());
- verify(mMockNetd).wakeupAddInterface(MOBILE_IFNAME, expectedPrefix, PACKET_WAKEUP_MARK,
- PACKET_WAKEUP_MASK);
+ verify(mMockNetd).wakeupAddInterface(MOBILE_IFNAME, expectedPrefix,
+ PACKET_WAKEUP_MARK_MASK, PACKET_WAKEUP_MARK_MASK);
} else {
verify(mMockNetd, never()).wakeupAddInterface(eq(MOBILE_IFNAME), anyString(), anyInt(),
anyInt());
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 955be12..1997215 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -179,7 +179,7 @@
anyInt(), anyString(), anyString(), anyString(), anyInt());
doReturn(false).when(mDeps).isMdnsDiscoveryManagerEnabled(any(Context.class));
doReturn(mDiscoveryManager).when(mDeps)
- .makeMdnsDiscoveryManager(any(), any(), any(), any());
+ .makeMdnsDiscoveryManager(any(), any(), any());
doReturn(mSocketProvider).when(mDeps).makeMdnsSocketProvider(any(), any(), any());
doReturn(mAdvertiser).when(mDeps).makeMdnsAdvertiser(any(), any(), any(), any());
mService = makeService();
diff --git a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
index 9e0435d..608e6d8 100644
--- a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
@@ -20,9 +20,12 @@
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static com.android.testutils.HandlerUtils.visibleOnHandlerThread;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -30,17 +33,20 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.longThat;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.ignoreStubs;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.AlarmManager;
import android.content.Context;
import android.content.res.Resources;
import android.net.INetd;
import android.net.ISocketKeepaliveCallback;
-import android.net.KeepalivePacketData;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MarkMaskParcel;
@@ -48,6 +54,7 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
+import android.net.SocketKeepalive;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -63,6 +70,7 @@
import androidx.annotation.Nullable;
import com.android.connectivity.resources.R;
+import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive;
import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -70,6 +78,7 @@
import libcore.util.HexEncoding;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -82,12 +91,15 @@
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public class AutomaticOnOffKeepaliveTrackerTest {
private static final String TAG = AutomaticOnOffKeepaliveTrackerTest.class.getSimpleName();
+ private static final int TEST_SLOT = 1;
private static final int TEST_NETID = 0xA85;
private static final int TEST_NETID_FWMARK = 0x0A85;
private static final int OTHER_NETID = 0x1A85;
@@ -95,6 +107,8 @@
private static final int TIMEOUT_MS = 30_000;
private static final int MOCK_RESOURCE_ID = 5;
private static final int TEST_KEEPALIVE_INTERVAL_SEC = 10;
+ private static final int TEST_KEEPALIVE_INVALID_INTERVAL_SEC = 9;
+
private AutomaticOnOffKeepaliveTracker mAOOKeepaliveTracker;
private HandlerThread mHandlerThread;
@@ -102,6 +116,8 @@
@Mock AutomaticOnOffKeepaliveTracker.Dependencies mDependencies;
@Mock Context mCtx;
@Mock AlarmManager mAlarmManager;
+ @Mock NetworkAgentInfo mNai;
+
TestKeepaliveTracker mKeepaliveTracker;
AOOTestHandler mTestHandler;
@@ -202,6 +218,37 @@
private static final byte[] TEST_RESPONSE_BYTES =
HexEncoding.decode(TEST_RESPONSE_HEX.toCharArray(), false);
+ private static class TestKeepaliveInfo {
+ private static List<Socket> sOpenSockets = new ArrayList<>();
+
+ public static void closeAllSockets() throws Exception {
+ for (final Socket socket : sOpenSockets) {
+ socket.close();
+ }
+ sOpenSockets.clear();
+ }
+
+ public final Socket socket;
+ public final Binder binder;
+ public final FileDescriptor fd;
+ public final ISocketKeepaliveCallback socketKeepaliveCallback;
+ public final Network underpinnedNetwork;
+ public final NattKeepalivePacketData kpd;
+
+ TestKeepaliveInfo(NattKeepalivePacketData kpd) throws Exception {
+ this.kpd = kpd;
+ socket = new Socket();
+ socket.bind(null);
+ sOpenSockets.add(socket);
+ fd = socket.getFileDescriptor$();
+
+ binder = new Binder();
+ socketKeepaliveCallback = mock(ISocketKeepaliveCallback.class);
+ doReturn(binder).when(socketKeepaliveCallback).asBinder();
+ underpinnedNetwork = mock(Network.class);
+ }
+ }
+
private class TestKeepaliveTracker extends KeepaliveTracker {
private KeepaliveInfo mKi;
@@ -231,6 +278,14 @@
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
+ mNai.networkCapabilities =
+ new NetworkCapabilities.Builder().addTransportType(TRANSPORT_CELLULAR).build();
+ mNai.networkInfo = new NetworkInfo(TYPE_MOBILE, 0 /* subtype */, "LTE", "LTE");
+ mNai.networkInfo.setDetailedState(
+ NetworkInfo.DetailedState.CONNECTED, "test reason", "test extra info");
+ doReturn(new Network(TEST_NETID)).when(mNai).network();
+ mNai.linkProperties = new LinkProperties();
+
doReturn(PERMISSION_GRANTED).when(mCtx).checkPermission(any() /* permission */,
anyInt() /* pid */, anyInt() /* uid */);
ConnectivityResources.setResourcesContextForTest(mCtx);
@@ -255,6 +310,11 @@
new AutomaticOnOffKeepaliveTracker(mCtx, mTestHandler, mDependencies);
}
+ @After
+ public void teardown() throws Exception {
+ TestKeepaliveInfo.closeAllSockets();
+ }
+
private final class AOOTestHandler extends Handler {
public AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive mLastAutoKi = null;
@@ -305,45 +365,70 @@
() -> assertFalse(mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
}
- @Test
- public void testAlarm() throws Exception {
+ private void triggerEventKeepalive(int slot, int reason) {
+ visibleOnHandlerThread(
+ mTestHandler,
+ () -> mAOOKeepaliveTracker.handleEventSocketKeepalive(mNai, slot, reason));
+ }
+
+ private TestKeepaliveInfo doStartNattKeepalive(int intervalSeconds) throws Exception {
final InetAddress srcAddress = InetAddress.getByAddress(
new byte[] { (byte) 192, 0, 0, (byte) 129 });
final int srcPort = 12345;
- final InetAddress dstAddress = InetAddress.getByAddress(new byte[] { 8, 8, 8, 8});
+ final InetAddress dstAddress = InetAddress.getByAddress(new byte[] {8, 8, 8, 8});
final int dstPort = 12345;
- final NetworkAgentInfo nai = mock(NetworkAgentInfo.class);
- nai.networkCapabilities = new NetworkCapabilities.Builder()
- .addTransportType(TRANSPORT_CELLULAR).build();
- nai.networkInfo = new NetworkInfo(TYPE_MOBILE, 0 /* subtype */, "LTE", "LTE");
- nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "test reason",
- "test extra info");
- nai.linkProperties = new LinkProperties();
- nai.linkProperties.addLinkAddress(new LinkAddress(srcAddress, 24));
+ mNai.linkProperties.addLinkAddress(new LinkAddress(srcAddress, 24));
- final Socket socket = new Socket();
- socket.bind(null);
- final FileDescriptor fd = socket.getFileDescriptor$();
- final IBinder binder = new Binder();
- final ISocketKeepaliveCallback cb = mock(ISocketKeepaliveCallback.class);
- doReturn(binder).when(cb).asBinder();
- final Network underpinnedNetwork = mock(Network.class);
-
- final KeepalivePacketData kpd = new NattKeepalivePacketData(srcAddress, srcPort,
+ final NattKeepalivePacketData kpd = new NattKeepalivePacketData(srcAddress, srcPort,
dstAddress, dstPort, new byte[] {1});
- final KeepaliveInfo ki = mKeepaliveTracker.new KeepaliveInfo(cb, nai, kpd,
- TEST_KEEPALIVE_INTERVAL_SEC, KeepaliveInfo.TYPE_NATT, fd);
+
+ final TestKeepaliveInfo testInfo = new TestKeepaliveInfo(kpd);
+
+ final KeepaliveInfo ki = mKeepaliveTracker.new KeepaliveInfo(
+ testInfo.socketKeepaliveCallback, mNai, kpd, intervalSeconds,
+ KeepaliveInfo.TYPE_NATT, testInfo.fd);
mKeepaliveTracker.setReturnedKeepaliveInfo(ki);
+ mAOOKeepaliveTracker.startNattKeepalive(mNai, testInfo.fd, intervalSeconds,
+ testInfo.socketKeepaliveCallback, srcAddress.toString(), srcPort,
+ dstAddress.toString(), dstPort, true /* automaticOnOffKeepalives */,
+ testInfo.underpinnedNetwork);
+ HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
+
+ return testInfo;
+ }
+
+ private TestKeepaliveInfo doStartNattKeepalive() throws Exception {
+ return doStartNattKeepalive(TEST_KEEPALIVE_INTERVAL_SEC);
+ }
+
+ private void doPauseKeepalive(AutomaticOnOffKeepalive autoKi) throws Exception {
+ setupResponseWithoutSocketExisting();
+ visibleOnHandlerThread(
+ mTestHandler,
+ () -> mAOOKeepaliveTracker.handleMonitorAutomaticKeepalive(autoKi, TEST_NETID));
+ }
+
+ private void doResumeKeepalive(AutomaticOnOffKeepalive autoKi) throws Exception {
+ setupResponseWithSocketExisting();
+ visibleOnHandlerThread(
+ mTestHandler,
+ () -> mAOOKeepaliveTracker.handleMonitorAutomaticKeepalive(autoKi, TEST_NETID));
+ }
+
+ private void doStopKeepalive(AutomaticOnOffKeepalive autoKi) throws Exception {
+ visibleOnHandlerThread(
+ mTestHandler,
+ () -> mAOOKeepaliveTracker.handleStopKeepalive(autoKi, SocketKeepalive.SUCCESS));
+ }
+
+ @Test
+ public void testAlarm() throws Exception {
// Mock elapsed real time to verify the alarm timer.
final long time = SystemClock.elapsedRealtime();
doReturn(time).when(mDependencies).getElapsedRealtime();
-
- mAOOKeepaliveTracker.startNattKeepalive(nai, fd, 10 /* intervalSeconds */, cb,
- srcAddress.toString(), srcPort, dstAddress.toString(), dstPort,
- true /* automaticOnOffKeepalives */, underpinnedNetwork);
- HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
final ArgumentCaptor<AlarmManager.OnAlarmListener> listenerCaptor =
ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
@@ -362,9 +447,8 @@
HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
assertNotNull(mTestHandler.mLastAutoKi);
- assertEquals(cb, mTestHandler.mLastAutoKi.getCallback());
- assertEquals(underpinnedNetwork, mTestHandler.mLastAutoKi.getUnderpinnedNetwork());
- socket.close();
+ assertEquals(testInfo.socketKeepaliveCallback, mTestHandler.mLastAutoKi.getCallback());
+ assertEquals(testInfo.underpinnedNetwork, mTestHandler.mLastAutoKi.getUnderpinnedNetwork());
}
private void setupResponseWithSocketExisting() throws Exception {
@@ -391,4 +475,301 @@
buffer.order(ByteOrder.nativeOrder());
return buffer;
}
+
+ private AutomaticOnOffKeepalive getAutoKiForBinder(IBinder binder) {
+ return visibleOnHandlerThread(
+ mTestHandler, () -> mAOOKeepaliveTracker.getKeepaliveForBinder(binder));
+ }
+
+ private void checkAndProcessKeepaliveStart(final NattKeepalivePacketData kpd) throws Exception {
+ checkAndProcessKeepaliveStart(TEST_SLOT, kpd);
+ }
+
+ private void checkAndProcessKeepaliveStart(
+ int slot, final NattKeepalivePacketData kpd) throws Exception {
+ verify(mNai).onStartNattSocketKeepalive(slot, TEST_KEEPALIVE_INTERVAL_SEC, kpd);
+ verify(mNai).onAddNattKeepalivePacketFilter(slot, kpd);
+ triggerEventKeepalive(slot, SocketKeepalive.SUCCESS);
+ }
+
+ private void checkAndProcessKeepaliveStop() throws Exception {
+ checkAndProcessKeepaliveStop(TEST_SLOT);
+ }
+
+ private void checkAndProcessKeepaliveStop(int slot) throws Exception {
+ verify(mNai).onStopSocketKeepalive(slot);
+ verify(mNai).onRemoveKeepalivePacketFilter(slot);
+ triggerEventKeepalive(slot, SocketKeepalive.SUCCESS);
+ }
+
+ @Test
+ public void testStartNattKeepalive_valid() throws Exception {
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+
+ final AutomaticOnOffKeepalive autoKi = getAutoKiForBinder(testInfo.binder);
+ assertNotNull(autoKi);
+ assertEquals(testInfo.socketKeepaliveCallback, autoKi.getCallback());
+
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testStartNattKeepalive_invalidInterval() throws Exception {
+ final TestKeepaliveInfo testInfo =
+ doStartNattKeepalive(TEST_KEEPALIVE_INVALID_INTERVAL_SEC);
+
+ assertNull(getAutoKiForBinder(testInfo.binder));
+
+ verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_INTERVAL);
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testHandleEventSocketKeepalive_startingFailureHardwareError() throws Exception {
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+
+ verify(mNai)
+ .onStartNattSocketKeepalive(TEST_SLOT, TEST_KEEPALIVE_INTERVAL_SEC, testInfo.kpd);
+ verify(mNai).onAddNattKeepalivePacketFilter(TEST_SLOT, testInfo.kpd);
+ // Network agent returns an error, fails to start the keepalive.
+ triggerEventKeepalive(TEST_SLOT, SocketKeepalive.ERROR_HARDWARE_ERROR);
+
+ checkAndProcessKeepaliveStop();
+
+ assertNull(getAutoKiForBinder(testInfo.binder));
+
+ verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_HARDWARE_ERROR);
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testHandleCheckKeepalivesStillValid_linkPropertiesChanged() throws Exception {
+ // Successful start of NATT keepalive.
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+
+ // Source address is removed from link properties by clearing.
+ mNai.linkProperties.clear();
+
+ // Check for valid keepalives
+ visibleOnHandlerThread(
+ mTestHandler, () -> mAOOKeepaliveTracker.handleCheckKeepalivesStillValid(mNai));
+
+ checkAndProcessKeepaliveStop();
+
+ assertNull(getAutoKiForBinder(testInfo.binder));
+
+ verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testStopKeepalive() throws Exception {
+ // Successful start of NATT keepalive.
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+
+ doStopKeepalive(getAutoKiForBinder(testInfo.binder));
+ checkAndProcessKeepaliveStop();
+
+ assertNull(getAutoKiForBinder(testInfo.binder));
+ verify(testInfo.socketKeepaliveCallback).onStopped();
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testPauseKeepalive() throws Exception {
+ // Successful start of NATT keepalive.
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+
+ doPauseKeepalive(getAutoKiForBinder(testInfo.binder));
+
+ checkAndProcessKeepaliveStop();
+ verify(testInfo.socketKeepaliveCallback).onPaused();
+
+ // Pausing does not cleanup the autoKi
+ assertNotNull(getAutoKiForBinder(testInfo.binder));
+
+ clearInvocations(mNai);
+ doStopKeepalive(getAutoKiForBinder(testInfo.binder));
+ // The keepalive is already stopped.
+ verify(mNai, never()).onStopSocketKeepalive(TEST_SLOT);
+ verify(mNai, never()).onRemoveKeepalivePacketFilter(TEST_SLOT);
+
+ // Stopping while paused still calls onStopped.
+ verify(testInfo.socketKeepaliveCallback).onStopped();
+ // autoKi is cleaned up.
+ assertNull(getAutoKiForBinder(testInfo.binder));
+
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testResumeKeepalive() throws Exception {
+ // Successful start of NATT keepalive.
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+
+ doPauseKeepalive(getAutoKiForBinder(testInfo.binder));
+ checkAndProcessKeepaliveStop();
+ verify(testInfo.socketKeepaliveCallback).onPaused();
+
+ clearInvocations(mNai);
+ doResumeKeepalive(getAutoKiForBinder(testInfo.binder));
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ assertNotNull(getAutoKiForBinder(testInfo.binder));
+ verify(testInfo.socketKeepaliveCallback).onResumed();
+
+ doStopKeepalive(getAutoKiForBinder(testInfo.binder));
+ checkAndProcessKeepaliveStop();
+ assertNull(getAutoKiForBinder(testInfo.binder));
+
+ verify(testInfo.socketKeepaliveCallback).onStopped();
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testResumeKeepalive_invalidSourceAddress() throws Exception {
+ // Successful start of NATT keepalive.
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+
+ doPauseKeepalive(getAutoKiForBinder(testInfo.binder));
+ checkAndProcessKeepaliveStop();
+ verify(testInfo.socketKeepaliveCallback).onPaused();
+
+ mNai.linkProperties.clear();
+
+ clearInvocations(mNai);
+ doResumeKeepalive(getAutoKiForBinder(testInfo.binder));
+ verify(mNai, never()).onStartNattSocketKeepalive(anyInt(), anyInt(), any());
+ verify(mNai, never()).onAddNattKeepalivePacketFilter(anyInt(), any());
+
+ assertNull(getAutoKiForBinder(testInfo.binder));
+
+ verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testResumeKeepalive_startingFailureHardwareError() throws Exception {
+ // Successful start of NATT keepalive.
+ final TestKeepaliveInfo testInfo = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo.kpd);
+ verify(testInfo.socketKeepaliveCallback).onStarted();
+
+ doPauseKeepalive(getAutoKiForBinder(testInfo.binder));
+ checkAndProcessKeepaliveStop();
+ verify(testInfo.socketKeepaliveCallback).onPaused();
+
+ clearInvocations(mNai);
+ doResumeKeepalive(getAutoKiForBinder(testInfo.binder));
+
+ verify(mNai)
+ .onStartNattSocketKeepalive(TEST_SLOT, TEST_KEEPALIVE_INTERVAL_SEC, testInfo.kpd);
+ verify(mNai).onAddNattKeepalivePacketFilter(TEST_SLOT, testInfo.kpd);
+ // Network agent returns error on starting the keepalive.
+ triggerEventKeepalive(TEST_SLOT, SocketKeepalive.ERROR_HARDWARE_ERROR);
+
+ checkAndProcessKeepaliveStop();
+
+ assertNull(getAutoKiForBinder(testInfo.binder));
+ verify(testInfo.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_HARDWARE_ERROR);
+ verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testStopAllKeepalives() throws Exception {
+ final TestKeepaliveInfo testInfo1 = doStartNattKeepalive();
+ final TestKeepaliveInfo testInfo2 = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(TEST_SLOT, testInfo1.kpd);
+ checkAndProcessKeepaliveStart(TEST_SLOT + 1, testInfo2.kpd);
+
+ verify(testInfo1.socketKeepaliveCallback).onStarted();
+ verify(testInfo2.socketKeepaliveCallback).onStarted();
+
+ // Pause the first keepalive
+ doPauseKeepalive(getAutoKiForBinder(testInfo1.binder));
+ checkAndProcessKeepaliveStop(TEST_SLOT);
+ verify(testInfo1.socketKeepaliveCallback).onPaused();
+
+ visibleOnHandlerThread(
+ mTestHandler,
+ () -> mAOOKeepaliveTracker.handleStopAllKeepalives(
+ mNai, SocketKeepalive.ERROR_INVALID_NETWORK));
+
+ // Note that checkAndProcessKeepaliveStop is not called since the network agent is assumed
+ // to be disconnected for a handleStopAllKeepalives call.
+ assertNull(getAutoKiForBinder(testInfo1.binder));
+ assertNull(getAutoKiForBinder(testInfo2.binder));
+
+ verify(testInfo1.socketKeepaliveCallback, never()).onStopped();
+ verify(testInfo2.socketKeepaliveCallback, never()).onStopped();
+ verify(testInfo1.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_NETWORK);
+ verify(testInfo2.socketKeepaliveCallback).onError(SocketKeepalive.ERROR_INVALID_NETWORK);
+
+ verifyNoMoreInteractions(ignoreStubs(testInfo1.socketKeepaliveCallback));
+ verifyNoMoreInteractions(ignoreStubs(testInfo2.socketKeepaliveCallback));
+ }
+
+ @Test
+ public void testTwoKeepalives_startAfterPause() throws Exception {
+ final TestKeepaliveInfo testInfo1 = doStartNattKeepalive();
+ checkAndProcessKeepaliveStart(testInfo1.kpd);
+ verify(testInfo1.socketKeepaliveCallback).onStarted();
+ assertNotNull(getAutoKiForBinder(testInfo1.binder));
+
+ final AutomaticOnOffKeepalive autoKi1 = getAutoKiForBinder(testInfo1.binder);
+ doPauseKeepalive(autoKi1);
+ checkAndProcessKeepaliveStop(TEST_SLOT);
+ verify(testInfo1.socketKeepaliveCallback).onPaused();
+ assertNotNull(getAutoKiForBinder(testInfo1.binder));
+
+ clearInvocations(mNai);
+ // Start the second keepalive while the first is paused.
+ final TestKeepaliveInfo testInfo2 = doStartNattKeepalive();
+ // The slot used is TEST_SLOT since it is now a free slot.
+ checkAndProcessKeepaliveStart(TEST_SLOT, testInfo2.kpd);
+ verify(testInfo2.socketKeepaliveCallback).onStarted();
+ assertNotNull(getAutoKiForBinder(testInfo2.binder));
+
+ clearInvocations(mNai);
+ doResumeKeepalive(autoKi1);
+ // The next free slot is TEST_SLOT + 1.
+ checkAndProcessKeepaliveStart(TEST_SLOT + 1, testInfo1.kpd);
+ verify(testInfo1.socketKeepaliveCallback).onResumed();
+
+ clearInvocations(mNai);
+ doStopKeepalive(autoKi1);
+ // TODO: The slot should be consistent with the checkAndProcessKeepaliveStart directly above
+ checkAndProcessKeepaliveStop(TEST_SLOT);
+ // TODO: onStopped should only be called on the first keepalive callback.
+ verify(testInfo1.socketKeepaliveCallback, never()).onStopped();
+ verify(testInfo2.socketKeepaliveCallback).onStopped();
+ assertNull(getAutoKiForBinder(testInfo1.binder));
+
+ clearInvocations(mNai);
+ assertNotNull(getAutoKiForBinder(testInfo2.binder));
+ doStopKeepalive(getAutoKiForBinder(testInfo2.binder));
+ // This slot should be consistent with its corresponding checkAndProcessKeepaliveStart.
+ // TODO: checkAndProcessKeepaliveStop should be called instead but the keepalive is
+ // unexpectedly already stopped above.
+ verify(mNai, never()).onStopSocketKeepalive(TEST_SLOT);
+ verify(mNai, never()).onRemoveKeepalivePacketFilter(TEST_SLOT);
+
+ verify(testInfo2.socketKeepaliveCallback).onStopped();
+ assertNull(getAutoKiForBinder(testInfo2.binder));
+
+ verifyNoMoreInteractions(ignoreStubs(testInfo1.socketKeepaliveCallback));
+ verifyNoMoreInteractions(ignoreStubs(testInfo2.socketKeepaliveCallback));
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 395e2bb..2d2819c 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -25,6 +25,9 @@
import static android.net.ConnectivityManager.NetworkCallback;
import static android.net.INetd.IF_STATE_DOWN;
import static android.net.INetd.IF_STATE_UP;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.RouteInfo.RTN_UNREACHABLE;
@@ -40,7 +43,6 @@
import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_AUTO;
import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_IPV4;
import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_IPV6;
-import static android.os.Build.VERSION_CODES.S_V2;
import static android.os.UserHandle.PER_USER_RANGE;
import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
import static android.telephony.CarrierConfigManager.KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
@@ -55,7 +57,6 @@
import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_ESP;
import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_UDP;
import static com.android.testutils.Cleanup.testAndCleanup;
-import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertArrayEquals;
@@ -166,6 +167,7 @@
import android.util.Pair;
import android.util.Range;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
@@ -173,12 +175,12 @@
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.internal.util.HexDump;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.DeviceIdleInternal;
import com.android.server.IpSecService;
import com.android.server.VpnTestBase;
import com.android.server.vcn.util.PersistableBundleUtils;
import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Rule;
@@ -196,6 +198,7 @@
import java.io.FileDescriptor;
import java.io.FileWriter;
import java.io.IOException;
+import java.io.StringWriter;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -213,7 +216,8 @@
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-import java.util.stream.Stream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Tests for {@link Vpn}.
@@ -221,9 +225,8 @@
* Build, install and run with:
* runtest frameworks-net -c com.android.server.connectivity.VpnTest
*/
-@RunWith(DevSdkIgnoreRunner.class)
+@RunWith(AndroidJUnit4.class)
@SmallTest
-@IgnoreUpTo(S_V2)
public class VpnTest extends VpnTestBase {
private static final String TAG = "VpnTest";
@@ -2608,6 +2611,81 @@
vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
}
+ private String getDump(@NonNull final Vpn vpn) {
+ final StringWriter sw = new StringWriter();
+ final IndentingPrintWriter writer = new IndentingPrintWriter(sw, "");
+ vpn.dump(writer);
+ writer.flush();
+ return sw.toString();
+ }
+
+ private int countMatches(@NonNull final Pattern regexp, @NonNull final String string) {
+ final Matcher m = regexp.matcher(string);
+ int i = 0;
+ while (m.find()) ++i;
+ return i;
+ }
+
+ @Test
+ public void testNCEventChanges() throws Exception {
+ final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .setLinkDownstreamBandwidthKbps(1000)
+ .setLinkUpstreamBandwidthKbps(500);
+
+ final Ikev2VpnProfile ikeProfile =
+ new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
+ .setAuthPsk(TEST_VPN_PSK)
+ .setBypassable(true /* isBypassable */)
+ .setAutomaticNattKeepaliveTimerEnabled(true)
+ .setAutomaticIpVersionSelectionEnabled(true)
+ .build();
+
+ final PlatformVpnSnapshot vpnSnapShot =
+ verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
+ createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+ ncBuilder.build(), false /* mtuSupportsIpv6 */,
+ true /* areLongLivedTcpConnectionsExpensive */);
+
+ // Calls to onCapabilitiesChanged will be thrown to the executor for execution ; by
+ // default this will incur a 10ms delay before it's executed, messing with the timing
+ // of the log and having the checks for counts in equals() below flake.
+ mExecutor.executeDirect = true;
+
+ // First nc changed triggered by verifySetupPlatformVpn
+ final Pattern pattern = Pattern.compile("Cap changed from", Pattern.MULTILINE);
+ final String stage1 = getDump(vpnSnapShot.vpn);
+ assertEquals(1, countMatches(pattern, stage1));
+
+ vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
+ final String stage2 = getDump(vpnSnapShot.vpn);
+ // Was the same caps, there should still be only 1 match
+ assertEquals(1, countMatches(pattern, stage2));
+
+ ncBuilder.setLinkDownstreamBandwidthKbps(1200)
+ .setLinkUpstreamBandwidthKbps(300);
+ vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
+ final String stage3 = getDump(vpnSnapShot.vpn);
+ // Was not an important change, should not be logged, still only 1 match
+ assertEquals(1, countMatches(pattern, stage3));
+
+ ncBuilder.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+ vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
+ final String stage4 = getDump(vpnSnapShot.vpn);
+ // Change to caps is important, should cause a new match
+ assertEquals(2, countMatches(pattern, stage4));
+
+ ncBuilder.removeCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+ ncBuilder.setLinkDownstreamBandwidthKbps(600);
+ vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
+ final String stage5 = getDump(vpnSnapShot.vpn);
+ // Change to caps is important, should cause a new match even with the unimportant change
+ assertEquals(3, countMatches(pattern, stage5));
+ }
+ // TODO : beef up event logs tests
+
private void verifyHandlingNetworkLoss(PlatformVpnSnapshot vpnSnapShot) throws Exception {
// Forget the #sendLinkProperties during first setup.
reset(mMockNetworkAgent);
@@ -3105,30 +3183,4 @@
} catch (Exception e) {
}
}
-
- private void setMockedNetworks(final Map<Network, NetworkCapabilities> networks) {
- doAnswer(invocation -> {
- final Network network = (Network) invocation.getArguments()[0];
- return networks.get(network);
- }).when(mConnectivityManager).getNetworkCapabilities(any());
- }
-
- // Need multiple copies of this, but Java's Stream objects can't be reused or
- // duplicated.
- private Stream<String> publicIpV4Routes() {
- return Stream.of(
- "0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4",
- "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6",
- "172.0.0.0/12", "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9",
- "173.0.0.0/8", "174.0.0.0/7", "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11",
- "192.160.0.0/13", "192.169.0.0/16", "192.170.0.0/15", "192.172.0.0/14",
- "192.176.0.0/12", "192.192.0.0/10", "193.0.0.0/8", "194.0.0.0/7",
- "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4");
- }
-
- private Stream<String> publicIpV6Routes() {
- return Stream.of(
- "::/1", "8000::/2", "c000::/3", "e000::/4", "f000::/5", "f800::/6",
- "fe00::/8", "2605:ef80:e:af1d::/64");
- }
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
index 89776e2..a54a521 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
@@ -18,6 +18,9 @@
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -62,21 +65,24 @@
private static final String SERVICE_TYPE_2 = "_test._tcp.local";
private static final Network NETWORK_1 = Mockito.mock(Network.class);
private static final Network NETWORK_2 = Mockito.mock(Network.class);
- private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_1 =
+ private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_1_NULL_NETWORK =
Pair.create(SERVICE_TYPE_1, null);
- private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_1_1 =
+ private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_1_NETWORK_1 =
Pair.create(SERVICE_TYPE_1, NETWORK_1);
- private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_2 =
+ private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_2_NULL_NETWORK =
Pair.create(SERVICE_TYPE_2, null);
- private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_2_2 =
+ private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_2_NETWORK_1 =
+ Pair.create(SERVICE_TYPE_2, NETWORK_1);
+ private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_2_NETWORK_2 =
Pair.create(SERVICE_TYPE_2, NETWORK_2);
@Mock private ExecutorProvider executorProvider;
@Mock private MdnsSocketClientBase socketClient;
- @Mock private MdnsServiceTypeClient mockServiceTypeClientOne;
- @Mock private MdnsServiceTypeClient mockServiceTypeClientOne1;
- @Mock private MdnsServiceTypeClient mockServiceTypeClientTwo;
- @Mock private MdnsServiceTypeClient mockServiceTypeClientTwo2;
+ @Mock private MdnsServiceTypeClient mockServiceTypeClientType1NullNetwork;
+ @Mock private MdnsServiceTypeClient mockServiceTypeClientType1Network1;
+ @Mock private MdnsServiceTypeClient mockServiceTypeClientType2NullNetwork;
+ @Mock private MdnsServiceTypeClient mockServiceTypeClientType2Network1;
+ @Mock private MdnsServiceTypeClient mockServiceTypeClientType2Network2;
@Mock MdnsServiceBrowserListener mockListenerOne;
@Mock MdnsServiceBrowserListener mockListenerTwo;
@@ -92,21 +98,29 @@
thread = new HandlerThread("MdnsDiscoveryManagerTests");
thread.start();
handler = new Handler(thread.getLooper());
- discoveryManager = new MdnsDiscoveryManager(executorProvider, socketClient, sharedLog,
- thread.getLooper()) {
+ doReturn(thread.getLooper()).when(socketClient).getLooper();
+ doReturn(true).when(socketClient).supportsRequestingSpecificNetworks();
+ discoveryManager = new MdnsDiscoveryManager(executorProvider, socketClient,
+ sharedLog) {
@Override
MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType,
@Nullable Network network) {
final Pair<String, Network> perNetworkServiceType =
Pair.create(serviceType, network);
- if (perNetworkServiceType.equals(PER_NETWORK_SERVICE_TYPE_1)) {
- return mockServiceTypeClientOne;
- } else if (perNetworkServiceType.equals(PER_NETWORK_SERVICE_TYPE_1_1)) {
- return mockServiceTypeClientOne1;
- } else if (perNetworkServiceType.equals(PER_NETWORK_SERVICE_TYPE_2)) {
- return mockServiceTypeClientTwo;
- } else if (perNetworkServiceType.equals(PER_NETWORK_SERVICE_TYPE_2_2)) {
- return mockServiceTypeClientTwo2;
+ if (perNetworkServiceType.equals(PER_NETWORK_SERVICE_TYPE_1_NULL_NETWORK)) {
+ return mockServiceTypeClientType1NullNetwork;
+ } else if (perNetworkServiceType.equals(
+ PER_NETWORK_SERVICE_TYPE_1_NETWORK_1)) {
+ return mockServiceTypeClientType1Network1;
+ } else if (perNetworkServiceType.equals(
+ PER_NETWORK_SERVICE_TYPE_2_NULL_NETWORK)) {
+ return mockServiceTypeClientType2NullNetwork;
+ } else if (perNetworkServiceType.equals(
+ PER_NETWORK_SERVICE_TYPE_2_NETWORK_1)) {
+ return mockServiceTypeClientType2Network1;
+ } else if (perNetworkServiceType.equals(
+ PER_NETWORK_SERVICE_TYPE_2_NETWORK_2)) {
+ return mockServiceTypeClientType2Network2;
}
return null;
}
@@ -143,11 +157,12 @@
final SocketCreationCallback callback = expectSocketCreationCallback(
SERVICE_TYPE_1, mockListenerOne, options);
runOnHandler(() -> callback.onSocketCreated(null /* network */));
- verify(mockServiceTypeClientOne).startSendAndReceive(mockListenerOne, options);
+ verify(mockServiceTypeClientType1NullNetwork).startSendAndReceive(mockListenerOne, options);
- when(mockServiceTypeClientOne.stopSendAndReceive(mockListenerOne)).thenReturn(true);
+ when(mockServiceTypeClientType1NullNetwork.stopSendAndReceive(mockListenerOne))
+ .thenReturn(true);
runOnHandler(() -> discoveryManager.unregisterListener(SERVICE_TYPE_1, mockListenerOne));
- verify(mockServiceTypeClientOne).stopSendAndReceive(mockListenerOne);
+ verify(mockServiceTypeClientType1NullNetwork).stopSendAndReceive(mockListenerOne);
verify(socketClient).stopDiscovery();
}
@@ -158,16 +173,16 @@
final SocketCreationCallback callback = expectSocketCreationCallback(
SERVICE_TYPE_1, mockListenerOne, options);
runOnHandler(() -> callback.onSocketCreated(null /* network */));
- verify(mockServiceTypeClientOne).startSendAndReceive(mockListenerOne, options);
+ verify(mockServiceTypeClientType1NullNetwork).startSendAndReceive(mockListenerOne, options);
runOnHandler(() -> callback.onSocketCreated(NETWORK_1));
- verify(mockServiceTypeClientOne1).startSendAndReceive(mockListenerOne, options);
+ verify(mockServiceTypeClientType1Network1).startSendAndReceive(mockListenerOne, options);
final SocketCreationCallback callback2 = expectSocketCreationCallback(
SERVICE_TYPE_2, mockListenerTwo, options);
runOnHandler(() -> callback2.onSocketCreated(null /* network */));
- verify(mockServiceTypeClientTwo).startSendAndReceive(mockListenerTwo, options);
+ verify(mockServiceTypeClientType2NullNetwork).startSendAndReceive(mockListenerTwo, options);
runOnHandler(() -> callback2.onSocketCreated(NETWORK_2));
- verify(mockServiceTypeClientTwo2).startSendAndReceive(mockListenerTwo, options);
+ verify(mockServiceTypeClientType2Network2).startSendAndReceive(mockListenerTwo, options);
}
@Test
@@ -177,103 +192,106 @@
final SocketCreationCallback callback = expectSocketCreationCallback(
SERVICE_TYPE_1, mockListenerOne, options1);
runOnHandler(() -> callback.onSocketCreated(null /* network */));
- verify(mockServiceTypeClientOne).startSendAndReceive(mockListenerOne, options1);
+ verify(mockServiceTypeClientType1NullNetwork).startSendAndReceive(
+ mockListenerOne, options1);
runOnHandler(() -> callback.onSocketCreated(NETWORK_1));
- verify(mockServiceTypeClientOne1).startSendAndReceive(mockListenerOne, options1);
+ verify(mockServiceTypeClientType1Network1).startSendAndReceive(mockListenerOne, options1);
final MdnsSearchOptions options2 =
MdnsSearchOptions.newBuilder().setNetwork(NETWORK_2).build();
final SocketCreationCallback callback2 = expectSocketCreationCallback(
SERVICE_TYPE_2, mockListenerTwo, options2);
runOnHandler(() -> callback2.onSocketCreated(NETWORK_2));
- verify(mockServiceTypeClientTwo2).startSendAndReceive(mockListenerTwo, options2);
+ verify(mockServiceTypeClientType2Network2).startSendAndReceive(mockListenerTwo, options2);
final MdnsPacket responseForServiceTypeOne = createMdnsPacket(SERVICE_TYPE_1);
final int ifIndex = 1;
runOnHandler(() -> discoveryManager.onResponseReceived(
responseForServiceTypeOne, ifIndex, null /* network */));
- verify(mockServiceTypeClientOne).processResponse(responseForServiceTypeOne, ifIndex,
- null /* network */);
- verify(mockServiceTypeClientOne1).processResponse(responseForServiceTypeOne, ifIndex,
- null /* network */);
- verify(mockServiceTypeClientTwo2).processResponse(responseForServiceTypeOne, ifIndex,
- null /* network */);
+ // Packets for network null are only processed by the ServiceTypeClient for network null
+ verify(mockServiceTypeClientType1NullNetwork).processResponse(responseForServiceTypeOne,
+ ifIndex, null /* network */);
+ verify(mockServiceTypeClientType1Network1, never()).processResponse(any(), anyInt(), any());
+ verify(mockServiceTypeClientType2Network2, never()).processResponse(any(), anyInt(), any());
final MdnsPacket responseForServiceTypeTwo = createMdnsPacket(SERVICE_TYPE_2);
runOnHandler(() -> discoveryManager.onResponseReceived(
responseForServiceTypeTwo, ifIndex, NETWORK_1));
- verify(mockServiceTypeClientOne).processResponse(responseForServiceTypeTwo, ifIndex,
- NETWORK_1);
- verify(mockServiceTypeClientOne1).processResponse(responseForServiceTypeTwo, ifIndex,
- NETWORK_1);
- verify(mockServiceTypeClientTwo2, never()).processResponse(responseForServiceTypeTwo,
+ verify(mockServiceTypeClientType1NullNetwork, never()).processResponse(any(), anyInt(),
+ eq(NETWORK_1));
+ verify(mockServiceTypeClientType1Network1).processResponse(responseForServiceTypeTwo,
ifIndex, NETWORK_1);
+ verify(mockServiceTypeClientType2Network2, never()).processResponse(any(), anyInt(),
+ eq(NETWORK_1));
final MdnsPacket responseForSubtype =
createMdnsPacket("subtype._sub._googlecast._tcp.local");
runOnHandler(() -> discoveryManager.onResponseReceived(
responseForSubtype, ifIndex, NETWORK_2));
- verify(mockServiceTypeClientOne).processResponse(responseForSubtype, ifIndex, NETWORK_2);
- verify(mockServiceTypeClientOne1, never()).processResponse(
+ verify(mockServiceTypeClientType1NullNetwork, never()).processResponse(
+ any(), anyInt(), eq(NETWORK_2));
+ verify(mockServiceTypeClientType1Network1, never()).processResponse(
+ any(), anyInt(), eq(NETWORK_2));
+ verify(mockServiceTypeClientType2Network2).processResponse(
responseForSubtype, ifIndex, NETWORK_2);
- verify(mockServiceTypeClientTwo2).processResponse(responseForSubtype, ifIndex, NETWORK_2);
}
@Test
public void testSocketCreatedAndDestroyed() throws IOException {
// Create a ServiceTypeClient for SERVICE_TYPE_1 and NETWORK_1
- final MdnsSearchOptions options1 =
+ final MdnsSearchOptions network1Options =
MdnsSearchOptions.newBuilder().setNetwork(NETWORK_1).build();
final SocketCreationCallback callback = expectSocketCreationCallback(
- SERVICE_TYPE_1, mockListenerOne, options1);
+ SERVICE_TYPE_1, mockListenerOne, network1Options);
runOnHandler(() -> callback.onSocketCreated(NETWORK_1));
- verify(mockServiceTypeClientOne1).startSendAndReceive(mockListenerOne, options1);
+ verify(mockServiceTypeClientType1Network1).startSendAndReceive(
+ mockListenerOne, network1Options);
- // Create a ServiceTypeClient for SERVICE_TYPE_2 and NETWORK_2
- final MdnsSearchOptions options2 =
- MdnsSearchOptions.newBuilder().setNetwork(NETWORK_2).build();
+ // Create a ServiceTypeClient for SERVICE_TYPE_2 and NETWORK_1
final SocketCreationCallback callback2 = expectSocketCreationCallback(
- SERVICE_TYPE_2, mockListenerTwo, options2);
- runOnHandler(() -> callback2.onSocketCreated(NETWORK_2));
- verify(mockServiceTypeClientTwo2).startSendAndReceive(mockListenerTwo, options2);
+ SERVICE_TYPE_2, mockListenerTwo, network1Options);
+ runOnHandler(() -> callback2.onSocketCreated(NETWORK_1));
+ verify(mockServiceTypeClientType2Network1).startSendAndReceive(
+ mockListenerTwo, network1Options);
// Receive a response, it should be processed on both clients.
final MdnsPacket response = createMdnsPacket(SERVICE_TYPE_1);
final int ifIndex = 1;
runOnHandler(() -> discoveryManager.onResponseReceived(
- response, ifIndex, null /* network */));
- verify(mockServiceTypeClientOne1).processResponse(response, ifIndex, null /* network */);
- verify(mockServiceTypeClientTwo2).processResponse(response, ifIndex, null /* network */);
+ response, ifIndex, NETWORK_1));
+ verify(mockServiceTypeClientType1Network1).processResponse(response, ifIndex, NETWORK_1);
+ verify(mockServiceTypeClientType2Network1).processResponse(response, ifIndex, NETWORK_1);
- // The client for NETWORK_1 receives the callback that the NETWORK_1 has been destroyed,
+ // The first callback receives a notification that the network has been destroyed,
// mockServiceTypeClientOne1 should send service removed notifications and remove from the
// list of clients.
runOnHandler(() -> callback.onAllSocketsDestroyed(NETWORK_1));
- verify(mockServiceTypeClientOne1).notifySocketDestroyed();
+ verify(mockServiceTypeClientType1Network1).notifySocketDestroyed();
- // Receive a response again, it should be processed only on mockServiceTypeClientTwo2.
- // Because the mockServiceTypeClientOne1 is removed from the list of clients, it is no
- // longer able to process responses.
+ // Receive a response again, it should be processed only on
+ // mockServiceTypeClientType2Network1. Because the mockServiceTypeClientType1Network1 is
+ // removed from the list of clients, it is no longer able to process responses.
runOnHandler(() -> discoveryManager.onResponseReceived(
- response, ifIndex, null /* network */));
- verify(mockServiceTypeClientOne1, times(1))
- .processResponse(response, ifIndex, null /* network */);
- verify(mockServiceTypeClientTwo2, times(2))
- .processResponse(response, ifIndex, null /* network */);
+ response, ifIndex, NETWORK_1));
+ // Still times(1) as a response was received once previously
+ verify(mockServiceTypeClientType1Network1, times(1))
+ .processResponse(response, ifIndex, NETWORK_1);
+ verify(mockServiceTypeClientType2Network1, times(2))
+ .processResponse(response, ifIndex, NETWORK_1);
- // The client for NETWORK_2 receives the callback that the NETWORK_1 has been destroyed,
+ // The client for NETWORK_1 receives the callback that the NETWORK_2 has been destroyed,
// mockServiceTypeClientTwo2 shouldn't send any notifications.
- runOnHandler(() -> callback2.onAllSocketsDestroyed(NETWORK_1));
- verify(mockServiceTypeClientTwo2, never()).notifySocketDestroyed();
+ runOnHandler(() -> callback2.onAllSocketsDestroyed(NETWORK_2));
+ verify(mockServiceTypeClientType2Network1, never()).notifySocketDestroyed();
- // Receive a response again, mockServiceTypeClientTwo2 is still in the list of clients, it's
- // still able to process responses.
+ // Receive a response again, mockServiceTypeClientType2Network1 is still in the list of
+ // clients, it's still able to process responses.
runOnHandler(() -> discoveryManager.onResponseReceived(
- response, ifIndex, null /* network */));
- verify(mockServiceTypeClientOne1, times(1))
- .processResponse(response, ifIndex, null /* network */);
- verify(mockServiceTypeClientTwo2, times(3))
- .processResponse(response, ifIndex, null /* network */);
+ response, ifIndex, NETWORK_1));
+ verify(mockServiceTypeClientType1Network1, times(1))
+ .processResponse(response, ifIndex, NETWORK_1);
+ verify(mockServiceTypeClientType2Network1, times(3))
+ .processResponse(response, ifIndex, NETWORK_1);
}
private MdnsPacket createMdnsPacket(String serviceType) {
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index c6137c6..87ba5d7 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -38,6 +38,7 @@
import android.os.HandlerThread;
import com.android.net.module.util.HexDump;
+import com.android.server.connectivity.mdns.MdnsSocketClientBase.SocketCreationCallback;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
@@ -66,7 +67,7 @@
@Mock private MdnsInterfaceSocket mSocket;
@Mock private MdnsServiceBrowserListener mListener;
@Mock private MdnsSocketClientBase.Callback mCallback;
- @Mock private MdnsSocketClientBase.SocketCreationCallback mSocketCreationCallback;
+ @Mock private SocketCreationCallback mSocketCreationCallback;
private MdnsMultinetworkSocketClient mSocketClient;
private Handler mHandler;
@@ -113,22 +114,36 @@
InetAddresses.parseNumericAddress("192.0.2.1"), 0 /* port */);
final DatagramPacket ipv6Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
InetAddresses.parseNumericAddress("2001:db8::"), 0 /* port */);
- doReturn(true).when(mSocket).hasJoinedIpv4();
- doReturn(true).when(mSocket).hasJoinedIpv6();
- doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+
+ final MdnsInterfaceSocket tetherIfaceSock1 = mock(MdnsInterfaceSocket.class);
+ final MdnsInterfaceSocket tetherIfaceSock2 = mock(MdnsInterfaceSocket.class);
+ for (MdnsInterfaceSocket socket : List.of(mSocket, tetherIfaceSock1, tetherIfaceSock2)) {
+ doReturn(true).when(socket).hasJoinedIpv4();
+ doReturn(true).when(socket).hasJoinedIpv6();
+ doReturn(createEmptyNetworkInterface()).when(socket).getInterface();
+ }
+
// Notify socket created
callback.onSocketCreated(mNetwork, mSocket, List.of());
verify(mSocketCreationCallback).onSocketCreated(mNetwork);
+ callback.onSocketCreated(null, tetherIfaceSock1, List.of());
+ verify(mSocketCreationCallback).onSocketCreated(null);
+ callback.onSocketCreated(null, tetherIfaceSock2, List.of());
+ verify(mSocketCreationCallback, times(2)).onSocketCreated(null);
// Send packet to IPv4 with target network and verify sending has been called.
mSocketClient.sendMulticastPacket(ipv4Packet, mNetwork);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mSocket).send(ipv4Packet);
+ verify(tetherIfaceSock1, never()).send(any());
+ verify(tetherIfaceSock2, never()).send(any());
// Send packet to IPv6 without target network and verify sending has been called.
- mSocketClient.sendMulticastPacket(ipv6Packet);
+ mSocketClient.sendMulticastPacket(ipv6Packet, null);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
- verify(mSocket).send(ipv6Packet);
+ verify(mSocket, never()).send(ipv6Packet);
+ verify(tetherIfaceSock1).send(ipv6Packet);
+ verify(tetherIfaceSock2).send(ipv6Packet);
}
@Test
@@ -181,61 +196,86 @@
@Test
public void testSocketRemovedAfterNetworkUnrequested() throws IOException {
- // Request a socket
- final SocketCallback callback = expectSocketCallback(mListener, mNetwork);
+ // Request sockets on all networks
+ final SocketCallback callback = expectSocketCallback(mListener, null);
final DatagramPacket ipv4Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
InetAddresses.parseNumericAddress("192.0.2.1"), 0 /* port */);
+
+ // Notify 3 socket created, including 2 tethered interfaces (null network)
+ final MdnsInterfaceSocket socket2 = mock(MdnsInterfaceSocket.class);
+ final MdnsInterfaceSocket socket3 = mock(MdnsInterfaceSocket.class);
doReturn(true).when(mSocket).hasJoinedIpv4();
doReturn(true).when(mSocket).hasJoinedIpv6();
- doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
- // Notify socket created
- callback.onSocketCreated(mNetwork, mSocket, List.of());
- verify(mSocketCreationCallback).onSocketCreated(mNetwork);
-
- // Send IPv4 packet and verify sending has been called.
- mSocketClient.sendMulticastPacket(ipv4Packet);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
- verify(mSocket).send(ipv4Packet);
-
- // Request another socket with null network
- final MdnsServiceBrowserListener listener2 = mock(MdnsServiceBrowserListener.class);
- final Network network2 = mock(Network.class);
- final MdnsInterfaceSocket socket2 = mock(MdnsInterfaceSocket.class);
- final SocketCallback callback2 = expectSocketCallback(listener2, null);
doReturn(true).when(socket2).hasJoinedIpv4();
doReturn(true).when(socket2).hasJoinedIpv6();
+ doReturn(true).when(socket3).hasJoinedIpv4();
+ doReturn(true).when(socket3).hasJoinedIpv6();
+ doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
doReturn(createEmptyNetworkInterface()).when(socket2).getInterface();
- // Notify socket created for two networks.
- callback2.onSocketCreated(mNetwork, mSocket, List.of());
- callback2.onSocketCreated(network2, socket2, List.of());
- verify(mSocketCreationCallback, times(2)).onSocketCreated(mNetwork);
- verify(mSocketCreationCallback).onSocketCreated(network2);
+ doReturn(createEmptyNetworkInterface()).when(socket3).getInterface();
- // Send IPv4 packet and verify sending to two sockets.
- mSocketClient.sendMulticastPacket(ipv4Packet);
+ callback.onSocketCreated(mNetwork, mSocket, List.of());
+ callback.onSocketCreated(null, socket2, List.of());
+ callback.onSocketCreated(null, socket3, List.of());
+ verify(mSocketCreationCallback).onSocketCreated(mNetwork);
+ verify(mSocketCreationCallback, times(2)).onSocketCreated(null);
+
+ // Send IPv4 packet on the non-null Network and verify sending has been called.
+ mSocketClient.sendMulticastPacket(ipv4Packet, mNetwork);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
- verify(mSocket, times(2)).send(ipv4Packet);
- verify(socket2).send(ipv4Packet);
+ verify(mSocket).send(ipv4Packet);
+ verify(socket2, never()).send(any());
+ verify(socket3, never()).send(any());
- // Unrequest another socket
+ // Request another socket with null network, get the same interfaces
+ final SocketCreationCallback socketCreationCb2 = mock(SocketCreationCallback.class);
+ final MdnsServiceBrowserListener listener2 = mock(MdnsServiceBrowserListener.class);
+
+ // requestSocket is called a second time
+ final ArgumentCaptor<SocketCallback> callback2Captor =
+ ArgumentCaptor.forClass(SocketCallback.class);
+ mHandler.post(() -> mSocketClient.notifyNetworkRequested(
+ listener2, null, socketCreationCb2));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ verify(mProvider, times(2)).requestSocket(eq(null), callback2Captor.capture());
+ final SocketCallback callback2 = callback2Captor.getAllValues().get(1);
+
+ // Notify socket created for all networks.
+ callback2.onSocketCreated(mNetwork, mSocket, List.of());
+ callback2.onSocketCreated(null, socket2, List.of());
+ callback2.onSocketCreated(null, socket3, List.of());
+ verify(socketCreationCb2).onSocketCreated(mNetwork);
+ verify(socketCreationCb2, times(2)).onSocketCreated(null);
+
+ // Send IPv4 packet to null network and verify sending to the 2 tethered interface sockets.
+ mSocketClient.sendMulticastPacket(ipv4Packet, null);
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ // ipv4Packet still sent only once on mSocket: times(1) matches the packet sent earlier on
+ // mNetwork
+ verify(mSocket, times(1)).send(ipv4Packet);
+ verify(socket2).send(ipv4Packet);
+ verify(socket3).send(ipv4Packet);
+
+ // Unregister the second request
mHandler.post(() -> mSocketClient.notifyNetworkUnrequested(listener2));
verify(mProvider, timeout(DEFAULT_TIMEOUT)).unrequestSocket(callback2);
- // Send IPv4 packet again and verify only sending via mSocket
- mSocketClient.sendMulticastPacket(ipv4Packet);
+ // Send IPv4 packet again and verify it's still sent a second time
+ mSocketClient.sendMulticastPacket(ipv4Packet, null);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
- verify(mSocket, times(3)).send(ipv4Packet);
- verify(socket2).send(ipv4Packet);
+ verify(socket2, times(2)).send(ipv4Packet);
+ verify(socket3, times(2)).send(ipv4Packet);
- // Unrequest remaining socket
+ // Unrequest remaining sockets
mHandler.post(() -> mSocketClient.notifyNetworkUnrequested(mListener));
verify(mProvider, timeout(DEFAULT_TIMEOUT)).unrequestSocket(callback);
// Send IPv4 packet and verify no more sending.
- mSocketClient.sendMulticastPacket(ipv4Packet);
+ mSocketClient.sendMulticastPacket(ipv4Packet, null);
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
- verify(mSocket, times(3)).send(ipv4Packet);
- verify(socket2).send(ipv4Packet);
+ verify(mSocket, times(1)).send(ipv4Packet);
+ verify(socket2, times(2)).send(ipv4Packet);
+ verify(socket3, times(2)).send(ipv4Packet);
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index 42d296b..da51240 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -705,34 +705,8 @@
}
@Test
- public void processResponse_notAllowRemoveSearch_shouldNotRemove() throws Exception {
- final String serviceInstanceName = "service-instance-1";
- client.startSendAndReceive(
- mockListenerOne,
- MdnsSearchOptions.newBuilder().build());
- Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
-
- // Process the initial response.
- client.processResponse(createResponse(
- serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
- Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
-
- // Clear the scheduled runnable.
- currentThreadExecutor.getAndClearLastScheduledRunnable();
-
- // Simulate the case where the response is after TTL.
- doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
- firstMdnsTask.run();
-
- // Verify removed callback was not called.
- verifyServiceRemovedNoCallback(mockListenerOne);
- }
-
- @Test
- @Ignore("MdnsConfigs is not configurable currently.")
- public void processResponse_allowSearchOptionsToRemoveExpiredService_shouldRemove()
+ public void processResponse_searchOptionsEnableServiceRemoval_shouldRemove()
throws Exception {
- //MdnsConfigsFlagsImpl.allowSearchOptionsToRemoveExpiredService.override(true);
final String serviceInstanceName = "service-instance-1";
client =
new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
@@ -742,7 +716,9 @@
return mockPacketWriter;
}
};
- client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
+ MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder().setRemoveExpiredService(
+ true).build();
+ client.startSendAndReceive(mockListenerOne, searchOptions);
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
// Process the initial response.
@@ -952,19 +928,15 @@
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
// Send twice for IPv4 and IPv6
inOrder.verify(mockSocketClient, times(2)).sendUnicastPacket(srvTxtQueryCaptor.capture(),
- eq(null) /* network */);
+ eq(mockNetwork));
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
- final List<MdnsRecord> srvTxtQuestions = srvTxtQueryPacket.questions;
- final String[] serviceName = Stream.concat(Stream.of(instanceName),
- Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
- assertFalse(srvTxtQuestions.stream().anyMatch(q -> q.getType() == MdnsRecord.TYPE_PTR));
- assertTrue(srvTxtQuestions.stream().anyMatch(q ->
- q.getType() == MdnsRecord.TYPE_SRV && Arrays.equals(q.name, serviceName)));
- assertTrue(srvTxtQuestions.stream().anyMatch(q ->
- q.getType() == MdnsRecord.TYPE_TXT && Arrays.equals(q.name, serviceName)));
+ final String[] serviceName = getTestServiceName(instanceName);
+ assertFalse(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_PTR));
+ assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_SRV, serviceName));
+ assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_TXT, serviceName));
// Process a response with SRV+TXT
final MdnsPacket srvTxtResponse = new MdnsPacket(
@@ -987,15 +959,12 @@
ArgumentCaptor.forClass(DatagramPacket.class);
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
inOrder.verify(mockSocketClient, times(2)).sendMulticastPacket(addressQueryCaptor.capture(),
- eq(null) /* network */);
+ eq(mockNetwork));
final MdnsPacket addressQueryPacket = MdnsPacket.parse(
new MdnsPacketReader(addressQueryCaptor.getValue()));
- final List<MdnsRecord> addressQueryQuestions = addressQueryPacket.questions;
- assertTrue(addressQueryQuestions.stream().anyMatch(q ->
- q.getType() == MdnsRecord.TYPE_A && Arrays.equals(q.name, hostname)));
- assertTrue(addressQueryQuestions.stream().anyMatch(q ->
- q.getType() == MdnsRecord.TYPE_AAAA && Arrays.equals(q.name, hostname)));
+ assertTrue(hasQuestion(addressQueryPacket, MdnsRecord.TYPE_A, hostname));
+ assertTrue(hasQuestion(addressQueryPacket, MdnsRecord.TYPE_AAAA, hostname));
// Process a response with address records
final MdnsPacket addressResponse = new MdnsPacket(
@@ -1028,6 +997,81 @@
}
@Test
+ public void testRenewTxtSrvInResolve() throws Exception {
+ client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+ mockDecoderClock, mockNetwork, mockSharedLog);
+
+ final String instanceName = "service-instance";
+ final String[] hostname = new String[] { "testhost "};
+ final String ipV4Address = "192.0.2.0";
+ final String ipV6Address = "2001:db8::";
+
+ final MdnsSearchOptions resolveOptions = MdnsSearchOptions.newBuilder()
+ .setResolveInstanceName(instanceName).build();
+
+ client.startSendAndReceive(mockListenerOne, resolveOptions);
+ InOrder inOrder = inOrder(mockListenerOne, mockSocketClient);
+
+ // Get the query for SRV/TXT
+ final ArgumentCaptor<DatagramPacket> srvTxtQueryCaptor =
+ ArgumentCaptor.forClass(DatagramPacket.class);
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+ // Send twice for IPv4 and IPv6
+ inOrder.verify(mockSocketClient, times(2)).sendUnicastPacket(srvTxtQueryCaptor.capture(),
+ eq(mockNetwork));
+
+ final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
+ new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
+
+ final String[] serviceName = getTestServiceName(instanceName);
+ assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_SRV, serviceName));
+ assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_TXT, serviceName));
+
+ // Process a response with all records
+ final MdnsPacket srvTxtResponse = new MdnsPacket(
+ 0 /* flags */,
+ Collections.emptyList() /* questions */,
+ List.of(
+ new MdnsServiceRecord(serviceName, TEST_ELAPSED_REALTIME,
+ true /* cacheFlush */, TEST_TTL, 0 /* servicePriority */,
+ 0 /* serviceWeight */, 1234 /* servicePort */, hostname),
+ new MdnsTextRecord(serviceName, TEST_ELAPSED_REALTIME,
+ true /* cacheFlush */, TEST_TTL,
+ Collections.emptyList() /* entries */),
+ new MdnsInetAddressRecord(hostname, TEST_ELAPSED_REALTIME,
+ true /* cacheFlush */, TEST_TTL,
+ InetAddresses.parseNumericAddress(ipV4Address)),
+ new MdnsInetAddressRecord(hostname, TEST_ELAPSED_REALTIME,
+ true /* cacheFlush */, TEST_TTL,
+ InetAddresses.parseNumericAddress(ipV6Address))),
+ Collections.emptyList() /* authorityRecords */,
+ Collections.emptyList() /* additionalRecords */);
+ client.processResponse(srvTxtResponse, INTERFACE_INDEX, mockNetwork);
+ inOrder.verify(mockListenerOne).onServiceNameDiscovered(any());
+ inOrder.verify(mockListenerOne).onServiceFound(any());
+
+ // Expect no query on the next run
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+ inOrder.verifyNoMoreInteractions();
+
+ // Advance time so 75% of TTL passes and re-execute
+ doReturn(TEST_ELAPSED_REALTIME + (long) (TEST_TTL * 0.75))
+ .when(mockDecoderClock).elapsedRealtime();
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+
+ // Expect a renewal query
+ final ArgumentCaptor<DatagramPacket> renewalQueryCaptor =
+ ArgumentCaptor.forClass(DatagramPacket.class);
+ // Second and later sends are sent as "expect multicast response" queries
+ inOrder.verify(mockSocketClient, times(2)).sendMulticastPacket(renewalQueryCaptor.capture(),
+ eq(mockNetwork));
+ final MdnsPacket renewalPacket = MdnsPacket.parse(
+ new MdnsPacketReader(renewalQueryCaptor.getValue()));
+ assertTrue(hasQuestion(renewalPacket, MdnsRecord.TYPE_SRV, serviceName));
+ assertTrue(hasQuestion(renewalPacket, MdnsRecord.TYPE_TXT, serviceName));
+ }
+
+ @Test
public void testProcessResponse_ResolveExcludesOtherServices() {
client = new MdnsServiceTypeClient(
SERVICE_TYPE, mockSocketClient, currentThreadExecutor, mockNetwork, mockSharedLog);
@@ -1255,21 +1299,35 @@
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
if (expectsUnicastResponse) {
verify(mockSocketClient).sendUnicastPacket(
- expectedIPv4Packets[index], null /* network */);
+ expectedIPv4Packets[index], mockNetwork);
if (multipleSocketDiscovery) {
verify(mockSocketClient).sendUnicastPacket(
- expectedIPv6Packets[index], null /* network */);
+ expectedIPv6Packets[index], mockNetwork);
}
} else {
verify(mockSocketClient).sendMulticastPacket(
- expectedIPv4Packets[index], null /* network */);
+ expectedIPv4Packets[index], mockNetwork);
if (multipleSocketDiscovery) {
verify(mockSocketClient).sendMulticastPacket(
- expectedIPv6Packets[index], null /* network */);
+ expectedIPv6Packets[index], mockNetwork);
}
}
}
+ private static String[] getTestServiceName(String instanceName) {
+ return Stream.concat(Stream.of(instanceName),
+ Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
+ }
+
+ private static boolean hasQuestion(MdnsPacket packet, int type) {
+ return hasQuestion(packet, type, null);
+ }
+
+ private static boolean hasQuestion(MdnsPacket packet, int type, @Nullable String[] name) {
+ return packet.questions.stream().anyMatch(q -> q.getType() == type
+ && (name == null || Arrays.equals(q.name, name)));
+ }
+
// A fake ScheduledExecutorService that keeps tracking the last scheduled Runnable and its delay
// time.
private class FakeExecutor extends ScheduledThreadPoolExecutor {
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
index 4b87556..4f56857 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -31,6 +31,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
@@ -40,7 +41,9 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkAddress;
@@ -49,6 +52,9 @@
import android.net.NetworkCapabilities;
import android.net.TetheringManager;
import android.net.TetheringManager.TetheringEventCallback;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pInfo;
+import android.net.wifi.p2p.WifiP2pManager;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
@@ -88,6 +94,7 @@
private static final String TAG = MdnsSocketProviderTest.class.getSimpleName();
private static final String TEST_IFACE_NAME = "test";
private static final String LOCAL_ONLY_IFACE_NAME = "local_only";
+ private static final String WIFI_P2P_IFACE_NAME = "p2p_wifi";
private static final String TETHERED_IFACE_NAME = "tethered";
private static final int TETHERED_IFACE_IDX = 32;
private static final long DEFAULT_TIMEOUT = 2000L;
@@ -136,11 +143,15 @@
doReturn(true).when(mTetheredIfaceWrapper).supportsMulticast();
doReturn(mLocalOnlyIfaceWrapper).when(mDeps)
.getNetworkInterfaceByName(LOCAL_ONLY_IFACE_NAME);
+ doReturn(mLocalOnlyIfaceWrapper).when(mDeps)
+ .getNetworkInterfaceByName(WIFI_P2P_IFACE_NAME);
doReturn(mTetheredIfaceWrapper).when(mDeps).getNetworkInterfaceByName(TETHERED_IFACE_NAME);
doReturn(mock(MdnsInterfaceSocket.class))
.when(mDeps).createMdnsInterfaceSocket(any(), anyInt(), any(), any());
doReturn(TETHERED_IFACE_IDX).when(mDeps).getNetworkInterfaceIndexByName(
TETHERED_IFACE_NAME);
+ doReturn(789).when(mDeps).getNetworkInterfaceIndexByName(
+ WIFI_P2P_IFACE_NAME);
final HandlerThread thread = new HandlerThread("MdnsSocketProviderTest");
thread.start();
mHandler = new Handler(thread.getLooper());
@@ -157,22 +168,41 @@
mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps, mLog);
}
+ private void runOnHandler(Runnable r) {
+ mHandler.post(r);
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ }
+
+ private BroadcastReceiver expectWifiP2PChangeBroadcastReceiver() {
+ final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(),
+ argThat(filter -> filter.hasAction(
+ WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)),
+ any(), any());
+ final BroadcastReceiver originalReceiver = receiverCaptor.getValue();
+ return new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ runOnHandler(() -> originalReceiver.onReceive(context, intent));
+ }
+ };
+ }
+
private void startMonitoringSockets() {
final ArgumentCaptor<NetworkCallback> nwCallbackCaptor =
ArgumentCaptor.forClass(NetworkCallback.class);
final ArgumentCaptor<TetheringEventCallback> teCallbackCaptor =
ArgumentCaptor.forClass(TetheringEventCallback.class);
- mHandler.post(mSocketProvider::startMonitoringSockets);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::startMonitoringSockets);
verify(mCm).registerNetworkCallback(any(), nwCallbackCaptor.capture(), any());
verify(mTm).registerTetheringEventCallback(any(), teCallbackCaptor.capture());
mNetworkCallback = nwCallbackCaptor.getValue();
mTetheringEventCallback = teCallbackCaptor.getValue();
- mHandler.post(mSocketProvider::startNetLinkMonitor);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::startNetLinkMonitor);
}
private static class TestNetlinkMonitor extends SocketNetlinkMonitor {
@@ -281,9 +311,8 @@
testLp.setInterfaceName(TEST_IFACE_NAME);
testLp.setLinkAddresses(List.of(LINKADDRV4));
final NetworkCapabilities testNc = makeCapabilities(transports);
- mHandler.post(() -> mNetworkCallback.onCapabilitiesChanged(TEST_NETWORK, testNc));
- mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mNetworkCallback.onCapabilitiesChanged(TEST_NETWORK, testNc));
+ runOnHandler(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
}
@Test
@@ -291,62 +320,53 @@
startMonitoringSockets();
final TestSocketCallback testCallback1 = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback1));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback1));
testCallback1.expectedNoCallback();
postNetworkAvailable(TRANSPORT_WIFI);
testCallback1.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
final TestSocketCallback testCallback2 = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
testCallback1.expectedNoCallback();
testCallback2.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
final TestSocketCallback testCallback3 = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(null /* network */, testCallback3));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(null /* network */, testCallback3));
testCallback1.expectedNoCallback();
testCallback2.expectedNoCallback();
testCallback3.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
- mHandler.post(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(
+ runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(
List.of(LOCAL_ONLY_IFACE_NAME)));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mLocalOnlyIfaceWrapper).getNetworkInterface();
testCallback1.expectedNoCallback();
testCallback2.expectedNoCallback();
testCallback3.expectedSocketCreatedForNetwork(null /* network */, List.of());
- mHandler.post(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
+ runOnHandler(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
List.of(TETHERED_IFACE_NAME)));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mTetheredIfaceWrapper).getNetworkInterface();
testCallback1.expectedNoCallback();
testCallback2.expectedNoCallback();
testCallback3.expectedSocketCreatedForNetwork(null /* network */, List.of());
- mHandler.post(() -> mSocketProvider.unrequestSocket(testCallback1));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.unrequestSocket(testCallback1));
testCallback1.expectedNoCallback();
testCallback2.expectedNoCallback();
testCallback3.expectedNoCallback();
- mHandler.post(() -> mNetworkCallback.onLost(TEST_NETWORK));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mNetworkCallback.onLost(TEST_NETWORK));
testCallback1.expectedNoCallback();
testCallback2.expectedInterfaceDestroyedForNetwork(TEST_NETWORK);
testCallback3.expectedInterfaceDestroyedForNetwork(TEST_NETWORK);
- mHandler.post(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(List.of()));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(List.of()));
testCallback1.expectedNoCallback();
testCallback2.expectedNoCallback();
testCallback3.expectedInterfaceDestroyedForNetwork(null /* network */);
- mHandler.post(() -> mSocketProvider.unrequestSocket(testCallback3));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.unrequestSocket(testCallback3));
testCallback1.expectedNoCallback();
testCallback2.expectedNoCallback();
// There was still a tethered interface, but no callback should be sent once unregistered
@@ -376,8 +396,7 @@
public void testDownstreamNetworkAddressUpdateFromNetlink() {
startMonitoringSockets();
final TestSocketCallback testCallbackAll = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(null /* network */, testCallbackAll));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(null /* network */, testCallbackAll));
// Address add message arrived before the interface is created.
RtNetlinkAddressMessage addIpv4AddrMsg = createNetworkAddressUpdateNetLink(
@@ -385,15 +404,13 @@
LINKADDRV4,
TETHERED_IFACE_IDX,
0 /* flags */);
- mHandler.post(
+ runOnHandler(
() -> mTestSocketNetLinkMonitor.processNetlinkMessage(addIpv4AddrMsg,
0 /* whenMs */));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
// Interface is created.
- mHandler.post(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
+ runOnHandler(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
List.of(TETHERED_IFACE_NAME)));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
verify(mTetheredIfaceWrapper).getNetworkInterface();
testCallbackAll.expectedSocketCreatedForNetwork(null /* network */, List.of(LINKADDRV4));
@@ -403,10 +420,9 @@
LINKADDRV4,
TETHERED_IFACE_IDX,
0 /* flags */);
- mHandler.post(
+ runOnHandler(
() -> mTestSocketNetLinkMonitor.processNetlinkMessage(removeIpv4AddrMsg,
0 /* whenMs */));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
testCallbackAll.expectedAddressesChangedForNetwork(null /* network */, List.of());
// New address added.
@@ -415,9 +431,8 @@
LINKADDRV6,
TETHERED_IFACE_IDX,
0 /* flags */);
- mHandler.post(() -> mTestSocketNetLinkMonitor.processNetlinkMessage(addIpv6AddrMsg,
+ runOnHandler(() -> mTestSocketNetLinkMonitor.processNetlinkMessage(addIpv6AddrMsg,
0 /* whenMs */));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
testCallbackAll.expectedAddressesChangedForNetwork(null /* network */, List.of(LINKADDRV6));
// Address updated
@@ -426,10 +441,9 @@
LINKADDRV6,
TETHERED_IFACE_IDX,
1 /* flags */);
- mHandler.post(
+ runOnHandler(
() -> mTestSocketNetLinkMonitor.processNetlinkMessage(updateIpv6AddrMsg,
0 /* whenMs */));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
testCallbackAll.expectedAddressesChangedForNetwork(null /* network */,
List.of(LINKADDRV6_FLAG_CHANGE));
}
@@ -439,8 +453,7 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
testCallback.expectedNoCallback();
postNetworkAvailable(TRANSPORT_WIFI);
@@ -449,8 +462,7 @@
final LinkProperties newTestLp = new LinkProperties();
newTestLp.setInterfaceName(TEST_IFACE_NAME);
newTestLp.setLinkAddresses(List.of(LINKADDRV4, LINKADDRV6));
- mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, newTestLp));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, newTestLp));
testCallback.expectedAddressesChangedForNetwork(
TEST_NETWORK, List.of(LINKADDRV4, LINKADDRV6));
}
@@ -458,8 +470,7 @@
@Test
public void testStartAndStopMonitoringSockets() {
// Stop monitoring sockets before start. Should not unregister any network callback.
- mHandler.post(mSocketProvider::requestStopWhenInactive);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::requestStopWhenInactive);
verify(mCm, never()).unregisterNetworkCallback(any(NetworkCallback.class));
verify(mTm, never()).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
@@ -467,39 +478,32 @@
startMonitoringSockets();
// Request a socket then unrequest it. Expect no network callback unregistration.
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
testCallback.expectedNoCallback();
- mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(()-> mSocketProvider.unrequestSocket(testCallback));
verify(mCm, never()).unregisterNetworkCallback(any(NetworkCallback.class));
verify(mTm, never()).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
// Request stop and it should unregister network callback immediately because there is no
// socket request.
- mHandler.post(mSocketProvider::requestStopWhenInactive);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::requestStopWhenInactive);
verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
verify(mTm, times(1)).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
// Start sockets monitoring and request a socket again.
- mHandler.post(mSocketProvider::startMonitoringSockets);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::startMonitoringSockets);
verify(mCm, times(2)).registerNetworkCallback(any(), any(NetworkCallback.class), any());
verify(mTm, times(2)).registerTetheringEventCallback(
any(), any(TetheringEventCallback.class));
final TestSocketCallback testCallback2 = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
testCallback2.expectedNoCallback();
// Try to stop monitoring sockets but should be ignored and wait until all socket are
// unrequested.
- mHandler.post(mSocketProvider::requestStopWhenInactive);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::requestStopWhenInactive);
verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
verify(mTm, times(1)).unregisterTetheringEventCallback(any());
// Unrequest the socket then network callbacks should be unregistered.
- mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback2));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(()-> mSocketProvider.unrequestSocket(testCallback2));
verify(mCm, times(2)).unregisterNetworkCallback(any(NetworkCallback.class));
verify(mTm, times(2)).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
}
@@ -510,24 +514,20 @@
// Request a socket with null network.
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(null, testCallback));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(null, testCallback));
testCallback.expectedNoCallback();
// Notify a LinkPropertiesChanged with TEST_NETWORK.
final LinkProperties testLp = new LinkProperties();
testLp.setInterfaceName(TEST_IFACE_NAME);
testLp.setLinkAddresses(List.of(LINKADDRV4));
- mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
verify(mTestNetworkIfaceWrapper, times(1)).getNetworkInterface();
testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
// Try to stop monitoring and unrequest the socket.
- mHandler.post(mSocketProvider::requestStopWhenInactive);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
- mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::requestStopWhenInactive);
+ runOnHandler(()-> mSocketProvider.unrequestSocket(testCallback));
// No callback sent when unregistered
testCallback.expectedNoCallback();
verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
@@ -535,13 +535,11 @@
// Start sockets monitoring and request a socket again. Expected no socket created callback
// because all saved LinkProperties has been cleared.
- mHandler.post(mSocketProvider::startMonitoringSockets);
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(mSocketProvider::startMonitoringSockets);
verify(mCm, times(2)).registerNetworkCallback(any(), any(NetworkCallback.class), any());
verify(mTm, times(2)).registerTetheringEventCallback(
any(), any(TetheringEventCallback.class));
- mHandler.post(() -> mSocketProvider.requestSocket(null, testCallback));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mSocketProvider.requestSocket(null, testCallback));
testCallback.expectedNoCallback();
// Notify a LinkPropertiesChanged with another network.
@@ -550,8 +548,7 @@
final Network otherNetwork = new Network(456);
otherLp.setInterfaceName("test2");
otherLp.setLinkAddresses(List.of(otherAddress));
- mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(otherNetwork, otherLp));
- HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ runOnHandler(() -> mNetworkCallback.onLinkPropertiesChanged(otherNetwork, otherLp));
verify(mTestNetworkIfaceWrapper, times(2)).getNetworkInterface();
testCallback.expectedSocketCreatedForNetwork(otherNetwork, List.of(otherAddress));
}
@@ -561,7 +558,7 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
postNetworkAvailable(TRANSPORT_CELLULAR);
testCallback.expectedNoCallback();
@@ -573,7 +570,7 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
postNetworkAvailable(TRANSPORT_BLUETOOTH);
testCallback.expectedNoCallback();
@@ -585,7 +582,7 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
postNetworkAvailable(TRANSPORT_BLUETOOTH);
testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
@@ -597,7 +594,7 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
postNetworkAvailable(TRANSPORT_BLUETOOTH);
testCallback.expectedNoCallback();
@@ -611,7 +608,7 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
postNetworkAvailable(TRANSPORT_VPN, TRANSPORT_WIFI);
testCallback.expectedNoCallback();
@@ -623,9 +620,146 @@
startMonitoringSockets();
final TestSocketCallback testCallback = new TestSocketCallback();
- mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ runOnHandler(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
postNetworkAvailable(TRANSPORT_WIFI);
testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
}
+
+ private Intent buildWifiP2PConnectionChangedIntent(boolean groupFormed) {
+ final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+ final WifiP2pInfo formedInfo = new WifiP2pInfo();
+ formedInfo.groupFormed = groupFormed;
+ final WifiP2pGroup group;
+ if (groupFormed) {
+ group = mock(WifiP2pGroup.class);
+ doReturn(WIFI_P2P_IFACE_NAME).when(group).getInterface();
+ } else {
+ group = null;
+ }
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, formedInfo);
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, group);
+ return intent;
+ }
+
+ @Test
+ public void testWifiP2PInterfaceChange() {
+ final BroadcastReceiver receiver = expectWifiP2PChangeBroadcastReceiver();
+ startMonitoringSockets();
+
+ // Request a socket with null network.
+ final TestSocketCallback testCallback = new TestSocketCallback();
+ runOnHandler(() -> mSocketProvider.requestSocket(null /* network */, testCallback));
+
+ // Wifi p2p is connected and the interface is up. Get a wifi p2p change intent then expect
+ // a socket creation.
+ final Intent formedIntent = buildWifiP2PConnectionChangedIntent(true /* groupFormed */);
+ receiver.onReceive(mContext, formedIntent);
+ verify(mLocalOnlyIfaceWrapper).getNetworkInterface();
+ testCallback.expectedSocketCreatedForNetwork(null /* network */, List.of());
+
+ // Wifi p2p is disconnected. Get a wifi p2p change intent then expect the socket destroy.
+ final Intent unformedIntent = buildWifiP2PConnectionChangedIntent(false /* groupFormed */);
+ receiver.onReceive(mContext, unformedIntent);
+ testCallback.expectedInterfaceDestroyedForNetwork(null /* network */);
+ }
+
+ @Test
+ public void testWifiP2PInterfaceChangeBeforeStartMonitoringSockets() {
+ final BroadcastReceiver receiver = expectWifiP2PChangeBroadcastReceiver();
+
+ // Get a wifi p2p change intent before start monitoring sockets.
+ final Intent formedIntent = buildWifiP2PConnectionChangedIntent(true /* groupFormed */);
+ receiver.onReceive(mContext, formedIntent);
+
+ // Start monitoring sockets and request a socket with null network.
+ startMonitoringSockets();
+ final TestSocketCallback testCallback = new TestSocketCallback();
+ runOnHandler(() -> mSocketProvider.requestSocket(null /* network */, testCallback));
+ verify(mLocalOnlyIfaceWrapper).getNetworkInterface();
+ testCallback.expectedSocketCreatedForNetwork(null /* network */, List.of());
+ }
+
+ @Test
+ public void testWifiP2PInterfaceChangeBeforeGetAllNetworksRequest() {
+ final BroadcastReceiver receiver = expectWifiP2PChangeBroadcastReceiver();
+ startMonitoringSockets();
+
+ // Get a wifi p2p change intent before request socket for all networks.
+ final Intent formedIntent = buildWifiP2PConnectionChangedIntent(true /* groupFormed */);
+ receiver.onReceive(mContext, formedIntent);
+
+ // Request a socket with null network.
+ final TestSocketCallback testCallback = new TestSocketCallback();
+ runOnHandler(() -> mSocketProvider.requestSocket(null /* network */, testCallback));
+ verify(mLocalOnlyIfaceWrapper).getNetworkInterface();
+ testCallback.expectedSocketCreatedForNetwork(null /* network */, List.of());
+ }
+
+ @Test
+ public void testNoDuplicatedSocketCreation() {
+ final BroadcastReceiver receiver = expectWifiP2PChangeBroadcastReceiver();
+ startMonitoringSockets();
+
+ // Request a socket with null network.
+ final TestSocketCallback testCallback = new TestSocketCallback();
+ runOnHandler(() -> mSocketProvider.requestSocket(null, testCallback));
+ testCallback.expectedNoCallback();
+
+ // Receive an interface added change for the wifi p2p interface. Expect a socket creation
+ // callback.
+ runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(
+ List.of(WIFI_P2P_IFACE_NAME)));
+ verify(mLocalOnlyIfaceWrapper, times(1)).getNetworkInterface();
+ testCallback.expectedSocketCreatedForNetwork(null /* network */, List.of());
+
+ // Receive a wifi p2p connected intent. Expect no callback because the socket is created.
+ final Intent formedIntent = buildWifiP2PConnectionChangedIntent(true /* groupFormed */);
+ receiver.onReceive(mContext, formedIntent);
+ testCallback.expectedNoCallback();
+
+ // Request other socket with null network. Should receive socket created callback once.
+ final TestSocketCallback testCallback2 = new TestSocketCallback();
+ runOnHandler(() -> mSocketProvider.requestSocket(null, testCallback2));
+ testCallback2.expectedSocketCreatedForNetwork(null /* network */, List.of());
+ testCallback2.expectedNoCallback();
+
+ // Receive a wifi p2p disconnected intent. Expect a socket destroy callback.
+ final Intent unformedIntent = buildWifiP2PConnectionChangedIntent(false /* groupFormed */);
+ receiver.onReceive(mContext, unformedIntent);
+ testCallback.expectedInterfaceDestroyedForNetwork(null /* network */);
+
+ // Receive an interface removed change for the wifi p2p interface. Expect no callback
+ // because the socket is destroyed.
+ runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(List.of()));
+ testCallback.expectedNoCallback();
+
+ // Receive a wifi p2p connected intent again. Expect a socket creation callback.
+ receiver.onReceive(mContext, formedIntent);
+ verify(mLocalOnlyIfaceWrapper, times(2)).getNetworkInterface();
+ testCallback.expectedSocketCreatedForNetwork(null /* network */, List.of());
+
+ // Receive an interface added change for the wifi p2p interface again. Expect no callback
+ // because the socket is created.
+ runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(
+ List.of(WIFI_P2P_IFACE_NAME)));
+ testCallback.expectedNoCallback();
+ }
+
+ @Test
+ public void testTetherInterfacesChangedBeforeGetAllNetworksRequest() {
+ startMonitoringSockets();
+
+ // Receive an interface added change for the wifi p2p interface. Expect a socket creation
+ // callback.
+ runOnHandler(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(
+ List.of(TETHERED_IFACE_NAME)));
+ verify(mTetheredIfaceWrapper, never()).getNetworkInterface();
+
+ // Request a socket with null network.
+ final TestSocketCallback testCallback = new TestSocketCallback();
+ runOnHandler(() -> mSocketProvider.requestSocket(null /* network */, testCallback));
+ verify(mTetheredIfaceWrapper).getNetworkInterface();
+ testCallback.expectedSocketCreatedForNetwork(null /* network */, List.of());
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitorTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitorTest.kt
new file mode 100644
index 0000000..c62a081
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitorTest.kt
@@ -0,0 +1,90 @@
+package com.android.server.connectivity.mdns.internal
+
+import android.net.LinkAddress
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import android.system.OsConstants
+import com.android.net.module.util.SharedLog
+import com.android.net.module.util.netlink.NetlinkConstants
+import com.android.net.module.util.netlink.RtNetlinkAddressMessage
+import com.android.net.module.util.netlink.StructIfaddrMsg
+import com.android.net.module.util.netlink.StructNlMsgHdr
+import com.android.server.connectivity.mdns.MdnsAdvertiserTest
+import com.android.server.connectivity.mdns.MdnsSocketProvider
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+private val LINKADDRV4 = LinkAddress("192.0.2.0/24")
+private val IFACE_IDX = 32
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+internal class SocketNetlinkMonitorTest {
+ private val thread = HandlerThread(MdnsAdvertiserTest::class.simpleName)
+ private val sharedlog = Mockito.mock(SharedLog::class.java)
+ private val netlinkMonitorCallBack =
+ Mockito.mock(MdnsSocketProvider.NetLinkMonitorCallBack::class.java)
+
+ @Before
+ fun setUp() {
+ thread.start()
+ }
+
+ @After
+ fun tearDown() {
+ thread.quitSafely()
+ }
+
+ @Test
+ fun testHandleDeprecatedNetlinkMessage() {
+ val socketNetlinkMonitor = SocketNetlinkMonitor(Handler(thread.looper), sharedlog,
+ netlinkMonitorCallBack)
+ val nlmsghdr = StructNlMsgHdr().apply {
+ nlmsg_type = NetlinkConstants.RTM_NEWADDR
+ nlmsg_flags = (StructNlMsgHdr.NLM_F_REQUEST.toInt()
+ or StructNlMsgHdr.NLM_F_ACK.toInt()).toShort()
+ nlmsg_seq = 1
+ }
+ val structIfaddrMsg = StructIfaddrMsg(OsConstants.AF_INET.toShort(),
+ LINKADDRV4.prefixLength.toShort(),
+ LINKADDRV4.flags.toShort(),
+ LINKADDRV4.scope.toShort(), IFACE_IDX)
+ // If the LinkAddress is not preferred, RTM_NEWADDR will not trigger
+ // addOrUpdateInterfaceAddress() callback.
+ val deprecatedAddNetLinkMessage = RtNetlinkAddressMessage(nlmsghdr, structIfaddrMsg,
+ LINKADDRV4.address, null /* structIfacacheInfo */,
+ LINKADDRV4.flags or OsConstants.IFA_F_DEPRECATED)
+ socketNetlinkMonitor.processNetlinkMessage(deprecatedAddNetLinkMessage, 0L /* whenMs */)
+ verify(netlinkMonitorCallBack, never()).addOrUpdateInterfaceAddress(eq(IFACE_IDX),
+ argThat { it.address == LINKADDRV4.address })
+
+ // If the LinkAddress is preferred, RTM_NEWADDR will trigger addOrUpdateInterfaceAddress()
+ // callback.
+ val preferredAddNetLinkMessage = RtNetlinkAddressMessage(nlmsghdr, structIfaddrMsg,
+ LINKADDRV4.address, null /* structIfacacheInfo */,
+ LINKADDRV4.flags or OsConstants.IFA_F_OPTIMISTIC)
+ socketNetlinkMonitor.processNetlinkMessage(preferredAddNetLinkMessage, 0L /* whenMs */)
+ verify(netlinkMonitorCallBack).addOrUpdateInterfaceAddress(eq(IFACE_IDX),
+ argThat { it.address == LINKADDRV4.address })
+
+ // Even if the LinkAddress is not preferred, RTM_DELADDR will trigger
+ // deleteInterfaceAddress() callback.
+ nlmsghdr.nlmsg_type = NetlinkConstants.RTM_DELADDR
+ val deprecatedDelNetLinkMessage = RtNetlinkAddressMessage(nlmsghdr, structIfaddrMsg,
+ LINKADDRV4.address, null /* structIfacacheInfo */,
+ LINKADDRV4.flags or OsConstants.IFA_F_DEPRECATED)
+ socketNetlinkMonitor.processNetlinkMessage(deprecatedDelNetLinkMessage, 0L /* whenMs */)
+ verify(netlinkMonitorCallBack).deleteInterfaceAddress(eq(IFACE_IDX),
+ argThat { it.address == LINKADDRV4.address })
+ }
+}