Merge "Revert "Add link-related methods at NetlinkUtils"" into main
diff --git a/Cronet/OWNERS b/Cronet/OWNERS
deleted file mode 100644
index c24680e..0000000
--- a/Cronet/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-set noparent
-file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
diff --git a/OWNERS_core_networking b/OWNERS_core_networking
index 6d8ed4a..078ccde 100644
--- a/OWNERS_core_networking
+++ b/OWNERS_core_networking
@@ -1,12 +1,13 @@
jchalard@google.com
junyulai@google.com
lorenzo@google.com
-martinwu@google.com
maze@google.com
motomuman@google.com
paulhu@google.com
prohr@google.com
reminv@google.com
-satk@google.com
xiaom@google.com
yuyanghuang@google.com
+
+martinwu@google.com #{LAST_RESORT_SUGGESTION}
+satk@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 94adc5b..b773ed8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -316,6 +316,14 @@
}
]
},
+ {
+ "name": "CtsHostsideNetworkTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
// Test with APK modules only, in cases where APEX is not supported, or the other modules
// were simply not updated
{
@@ -414,15 +422,6 @@
"exclude-annotation": "androidx.test.filters.RequiresDevice"
}
]
- },
- // TODO: upgrade to presubmit. Postsubmit on virtual devices to monitor flakiness only.
- {
- "name": "CtsHostsideNetworkTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.RequiresDevice"
- }
- ]
}
],
"imports": [
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 70b38a4..5cf5528 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -79,6 +79,7 @@
],
defaults: ["TetheringExternalLibs"],
libs: [
+ "framework-annotations-lib",
"framework-tethering.impl",
],
manifest: "AndroidManifestBase.xml",
@@ -203,6 +204,7 @@
use_embedded_native_libs: true,
privapp_allowlist: ":privapp_allowlist_com.android.tethering",
apex_available: ["com.android.tethering"],
+ updatable: true,
}
android_app {
@@ -220,6 +222,7 @@
lint: {
error_checks: ["NewApi"],
},
+ updatable: true,
}
sdk {
diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
index 0df9047..af061e4 100644
--- a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
@@ -198,4 +198,13 @@
public String toString() {
return "Netd used";
}
+
+ @Override
+ public int getLastMaxConnectionAndResetToCurrent() {
+ return 0;
+ }
+
+ @Override
+ public void clearConnectionCounters() {
+ }
}
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index e6e99f4..b460f0d 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -19,6 +19,7 @@
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_ACTIVE_SESSIONS_METRICS;
import android.system.ErrnoException;
import android.system.Os;
@@ -108,6 +109,22 @@
// TODO: Add IPv6 rule count.
private final SparseArray<Integer> mRule4CountOnUpstream = new SparseArray<>();
+ private final boolean mSupportActiveSessionsMetrics;
+ /**
+ * Tracks the current number of tethering connections and the maximum
+ * observed since the last metrics collection. Used to provide insights
+ * into the distribution of active tethering sessions for metrics reporting.
+
+ * These variables are accessed on the handler thread, which includes:
+ * 1. ConntrackEvents signaling the addition or removal of an IPv4 rule.
+ * 2. ConntrackEvents indicating the removal of a tethering client,
+ * triggering the removal of associated rules.
+ * 3. Removal of the last IpServer, which resets counters to handle
+ * potential synchronization issues.
+ */
+ private int mLastMaxConnectionCount = 0;
+ private int mCurrentConnectionCount = 0;
+
public BpfCoordinatorShimImpl(@NonNull final Dependencies deps) {
mLog = deps.getSharedLog().forSubComponent(TAG);
@@ -156,6 +173,9 @@
} catch (ErrnoException e) {
mLog.e("Could not clear mBpfDevMap: " + e);
}
+
+ mSupportActiveSessionsMetrics = deps.isFeatureEnabled(deps.getContext(),
+ TETHER_ACTIVE_SESSIONS_METRICS);
}
@Override
@@ -350,6 +370,12 @@
final int upstreamIfindex = (int) key.iif;
int count = mRule4CountOnUpstream.get(upstreamIfindex, 0 /* default */);
mRule4CountOnUpstream.put(upstreamIfindex, ++count);
+
+ if (mSupportActiveSessionsMetrics) {
+ mCurrentConnectionCount++;
+ mLastMaxConnectionCount = Math.max(mCurrentConnectionCount,
+ mLastMaxConnectionCount);
+ }
} else {
mBpfUpstream4Map.insertEntry(key, value);
}
@@ -385,6 +411,10 @@
} else {
mRule4CountOnUpstream.put(upstreamIfindex, count);
}
+
+ if (mSupportActiveSessionsMetrics) {
+ mCurrentConnectionCount--;
+ }
} else {
if (!mBpfUpstream4Map.deleteEntry(key)) return false; // Rule did not exist
}
@@ -465,14 +495,16 @@
@Override
public String toString() {
- return String.join(", ", new String[] {
- mapStatus(mBpfDownstream6Map, "mBpfDownstream6Map"),
- mapStatus(mBpfUpstream6Map, "mBpfUpstream6Map"),
- mapStatus(mBpfDownstream4Map, "mBpfDownstream4Map"),
- mapStatus(mBpfUpstream4Map, "mBpfUpstream4Map"),
- mapStatus(mBpfStatsMap, "mBpfStatsMap"),
- mapStatus(mBpfLimitMap, "mBpfLimitMap"),
- mapStatus(mBpfDevMap, "mBpfDevMap")
+ return String.join(", ", new String[]{
+ mapStatus(mBpfDownstream6Map, "mBpfDownstream6Map"),
+ mapStatus(mBpfUpstream6Map, "mBpfUpstream6Map"),
+ mapStatus(mBpfDownstream4Map, "mBpfDownstream4Map"),
+ mapStatus(mBpfUpstream4Map, "mBpfUpstream4Map"),
+ mapStatus(mBpfStatsMap, "mBpfStatsMap"),
+ mapStatus(mBpfLimitMap, "mBpfLimitMap"),
+ mapStatus(mBpfDevMap, "mBpfDevMap"),
+ "mCurrentConnectionCount=" + mCurrentConnectionCount,
+ "mLastMaxConnectionCount=" + mLastMaxConnectionCount
});
}
@@ -507,4 +539,17 @@
return 0;
}
+
+ /** Get last max connection count and reset to current count. */
+ public int getLastMaxConnectionAndResetToCurrent() {
+ final int ret = mLastMaxConnectionCount;
+ mLastMaxConnectionCount = mCurrentConnectionCount;
+ return ret;
+ }
+
+ /** Clear current connection count. */
+ public void clearConnectionCounters() {
+ mCurrentConnectionCount = 0;
+ mLastMaxConnectionCount = 0;
+ }
}
diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
index 026b1c3..cb8bcc9 100644
--- a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
+++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
@@ -202,5 +202,11 @@
* Remove interface index mapping.
*/
public abstract boolean removeDevMap(int ifIndex);
+
+ /** Get last max connection count and reset to current count. */
+ public abstract int getLastMaxConnectionAndResetToCurrent();
+
+ /** Clear current connection count. */
+ public abstract void clearConnectionCounters();
}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 5aca642..411971d 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -430,7 +430,7 @@
// Used to dispatch legacy ConnectivityManager methods that expect tethering to be able to
// return results and perform operations synchronously.
// TODO: remove once there are no callers of these legacy methods.
- private class RequestDispatcher {
+ private static class RequestDispatcher {
private final ConditionVariable mWaiting;
public volatile int mRemoteResult;
@@ -446,8 +446,8 @@
mWaiting = new ConditionVariable();
}
- int waitForResult(final RequestHelper request) {
- getConnector(c -> request.runRequest(c, mListener));
+ int waitForResult(final RequestHelper request, final TetheringManager mgr) {
+ mgr.getConnector(c -> request.runRequest(c, mListener));
if (!mWaiting.block(DEFAULT_TIMEOUT_MS)) {
throw new IllegalStateException("Callback timeout");
}
@@ -603,7 +603,7 @@
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
- });
+ }, this);
}
/**
@@ -635,7 +635,7 @@
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
- });
+ }, this);
}
/**
@@ -663,7 +663,7 @@
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
- });
+ }, this);
}
/**
@@ -1751,7 +1751,7 @@
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
- });
+ }, this);
return ret == TETHER_ERROR_NO_ERROR;
}
@@ -1800,6 +1800,6 @@
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
- });
+ }, this);
}
}
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index b807544..7a6df88 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -124,6 +124,8 @@
// TODO: have PanService use some visible version of this constant
private static final String BLUETOOTH_IFACE_ADDR = "192.168.44.1/24";
+ private static final String LEGACY_WIFI_P2P_IFACE_ADDRESS = "192.168.49.1/24";
+
// TODO: have this configurable
private static final int DHCP_LEASE_TIME_SECS = 3600;
@@ -249,6 +251,7 @@
private final LinkProperties mLinkProperties;
private final boolean mUsingLegacyDhcp;
private final int mP2pLeasesSubnetPrefixLength;
+ private final boolean mIsWifiP2pDedicatedIpEnabled;
private final Dependencies mDeps;
@@ -313,6 +316,7 @@
mLinkProperties = new LinkProperties();
mUsingLegacyDhcp = config.useLegacyDhcpServer();
mP2pLeasesSubnetPrefixLength = config.getP2pLeasesSubnetPrefixLength();
+ mIsWifiP2pDedicatedIpEnabled = config.shouldEnableWifiP2pDedicatedIp();
mPrivateAddressCoordinator = addressCoordinator;
mDeps = deps;
mTetheringMetrics = tetheringMetrics;
@@ -698,11 +702,18 @@
return (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) && !SdkLevel.isAtLeastT();
}
+ private boolean shouldUseWifiP2pDedicatedIp() {
+ return mIsWifiP2pDedicatedIpEnabled
+ && mInterfaceType == TetheringManager.TETHERING_WIFI_P2P;
+ }
+
private LinkAddress requestIpv4Address(final int scope, final boolean useLastAddress) {
if (mStaticIpv4ServerAddr != null) return mStaticIpv4ServerAddr;
if (shouldNotConfigureBluetoothInterface()) return new LinkAddress(BLUETOOTH_IFACE_ADDR);
+ if (shouldUseWifiP2pDedicatedIp()) return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS);
+
return mPrivateAddressCoordinator.requestDownstreamAddress(this, scope, useLastAddress);
}
@@ -1148,6 +1159,7 @@
case CMD_SERVICE_FAILED_TO_START:
mLog.e("start serving fail, error: " + message.arg1);
transitionTo(mInitialState);
+ break;
default:
return false;
}
@@ -1393,8 +1405,28 @@
@Override
public void enter() {
mLastError = TETHER_ERROR_NO_ERROR;
+ // TODO: clean this up after the synchronous state machine is fully rolled out. Clean up
+ // can be directly triggered after calling IpServer.stop() inside Tethering.java.
sendInterfaceState(STATE_UNAVAILABLE);
}
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_IPV6_TETHER_UPDATE:
+ // sendInterfaceState(STATE_UNAVAILABLE) triggers
+ // handleInterfaceServingStateInactive which in turn cleans up IPv6 tethering
+ // (and calls into IpServer one more time). At this point, this is the only
+ // message we potentially see in this state because this IpServer has already
+ // been removed from mTetherStates before transitioning to this State; however,
+ // handleInterfaceServiceStateInactive passes a reference.
+ // TODO: This can be removed once SyncStateMachine is rolled out and the
+ // teardown path is cleaned up.
+ return true;
+ default:
+ return false;
+ }
+ }
}
class WaitingForRestartState extends State {
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 89e06da..75ab9ec 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -27,6 +27,7 @@
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.ETH_P_IPV6;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.net.module.util.NetworkStackConstants.IPV4_MIN_MTU;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN;
import static com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent;
@@ -334,7 +335,6 @@
};
// TODO: add BpfMap<TetherDownstream64Key, TetherDownstream64Value> retrieving function.
- @VisibleForTesting
public abstract static class Dependencies {
/** Get handler. */
@NonNull public abstract Handler getHandler();
@@ -585,14 +585,10 @@
if (mHandler.hasCallbacks(mScheduledConntrackMetricsSampling)) {
mHandler.removeCallbacks(mScheduledConntrackMetricsSampling);
}
- final int currentCount = mBpfConntrackEventConsumer.getCurrentConnectionCount();
- if (currentCount != 0) {
- Log.wtf(TAG, "Unexpected CurrentConnectionCount: " + currentCount);
- }
// Avoid sending metrics when tethering is about to close.
// This leads to a missing final sample before disconnect
// but avoids possibly duplicating the last metric in the upload.
- mBpfConntrackEventConsumer.clearConnectionCounters();
+ mBpfCoordinatorShim.clearConnectionCounters();
}
// Stop scheduled polling stats and poll the latest stats from BPF maps.
if (mHandler.hasCallbacks(mScheduledPollingStats)) {
@@ -1091,10 +1087,6 @@
for (final Tether4Key k : deleteDownstreamRuleKeys) {
mBpfCoordinatorShim.tetherOffloadRuleRemove(DOWNSTREAM, k);
}
- if (mSupportActiveSessionsMetrics) {
- mBpfConntrackEventConsumer.decreaseCurrentConnectionCount(
- deleteUpstreamRuleKeys.size());
- }
// Cleanup each upstream interface by a set which avoids duplicated work on the same
// upstream interface. Cleaning up the same interface twice (or more) here may raise
@@ -1367,10 +1359,6 @@
pw.println();
pw.println("mSupportActiveSessionsMetrics: " + mSupportActiveSessionsMetrics);
- pw.println("getLastMaxConnectionCount: "
- + mBpfConntrackEventConsumer.getLastMaxConnectionCount());
- pw.println("getCurrentConnectionCount: "
- + mBpfConntrackEventConsumer.getCurrentConnectionCount());
}
private void dumpStats(@NonNull IndentingPrintWriter pw) {
@@ -2062,21 +2050,6 @@
// while TCP status is established.
@VisibleForTesting
class BpfConntrackEventConsumer implements ConntrackEventConsumer {
- /**
- * Tracks the current number of tethering connections and the maximum
- * observed since the last metrics collection. Used to provide insights
- * into the distribution of active tethering sessions for metrics reporting.
-
- * These variables are accessed on the handler thread, which includes:
- * 1. ConntrackEvents signaling the addition or removal of an IPv4 rule.
- * 2. ConntrackEvents indicating the removal of a tethering client,
- * triggering the removal of associated rules.
- * 3. Removal of the last IpServer, which resets counters to handle
- * potential synchronization issues.
- */
- private int mLastMaxConnectionCount = 0;
- private int mCurrentConnectionCount = 0;
-
// The upstream4 and downstream4 rules are built as the following tables. Only raw ip
// upstream interface is supported. Note that the field "lastUsed" is only updated by
// BPF program which records the last used time for a given rule.
@@ -2210,10 +2183,6 @@
return;
}
- if (mSupportActiveSessionsMetrics) {
- decreaseCurrentConnectionCount(1);
- }
-
maybeClearLimit(upstreamIndex);
return;
}
@@ -2237,40 +2206,12 @@
+ ", downstream: " + addedDownstream + ")");
return;
}
- if (mSupportActiveSessionsMetrics && addedUpstream && addedDownstream) {
- mCurrentConnectionCount++;
- mLastMaxConnectionCount = Math.max(mCurrentConnectionCount,
- mLastMaxConnectionCount);
- }
}
+ }
- public int getLastMaxConnectionAndResetToCurrent() {
- final int ret = mLastMaxConnectionCount;
- mLastMaxConnectionCount = mCurrentConnectionCount;
- return ret;
- }
-
- /** For dumping current state only. */
- public int getLastMaxConnectionCount() {
- return mLastMaxConnectionCount;
- }
-
- public int getCurrentConnectionCount() {
- return mCurrentConnectionCount;
- }
-
- public void decreaseCurrentConnectionCount(int count) {
- mCurrentConnectionCount -= count;
- if (mCurrentConnectionCount < 0) {
- Log.wtf(TAG, "Unexpected mCurrentConnectionCount: "
- + mCurrentConnectionCount);
- }
- }
-
- public void clearConnectionCounters() {
- mCurrentConnectionCount = 0;
- mLastMaxConnectionCount = 0;
- }
+ @VisibleForTesting(visibility = PRIVATE)
+ public int getLastMaxConnectionAndResetToCurrent() {
+ return mBpfCoordinatorShim.getLastMaxConnectionAndResetToCurrent();
}
@VisibleForTesting
@@ -2611,7 +2552,7 @@
private void uploadConntrackMetricsSample() {
mDeps.sendTetheringActiveSessionsReported(
- mBpfConntrackEventConsumer.getLastMaxConnectionAndResetToCurrent());
+ mBpfCoordinatorShim.getLastMaxConnectionAndResetToCurrent());
}
private void schedulePollingStats() {
diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index 528991f..50f82cf 100644
--- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -29,10 +29,11 @@
import static java.util.Arrays.asList;
import android.content.Context;
-import android.net.ConnectivityManager;
import android.net.IpPrefix;
import android.net.LinkAddress;
+import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.ip.IpServer;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -42,6 +43,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.DeviceConfigUtils;
import java.net.Inet4Address;
import java.net.InetAddress;
@@ -51,6 +53,7 @@
import java.util.List;
import java.util.Random;
import java.util.Set;
+import java.util.function.Supplier;
/**
* This class coordinate IP addresses conflict problem.
@@ -63,8 +66,12 @@
* @hide
*/
public class PrivateAddressCoordinator {
+ // WARNING: Keep in sync with chooseDownstreamAddress
public static final int PREFIX_LENGTH = 24;
+ public static final String TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION =
+ "tether_force_random_prefix_base_selection";
+
// Upstream monitor would be stopped when tethering is down. When tethering restart, downstream
// address may be requested before coordinator get current upstream notification. To ensure
// coordinator do not select conflict downstream prefix, mUpstreamPrefixMap would not be cleared
@@ -75,18 +82,44 @@
private static final String LEGACY_WIFI_P2P_IFACE_ADDRESS = "192.168.49.1/24";
private static final String LEGACY_BLUETOOTH_IFACE_ADDRESS = "192.168.44.1/24";
private final List<IpPrefix> mTetheringPrefixes;
- private final ConnectivityManager mConnectivityMgr;
- private final TetheringConfiguration mConfig;
+ // A supplier that returns ConnectivityManager#getAllNetworks.
+ private final Supplier<Network[]> mGetAllNetworksSupplier;
+ private final Dependencies mDeps;
// keyed by downstream type(TetheringManager.TETHERING_*).
private final ArrayMap<AddressKey, LinkAddress> mCachedAddresses;
private final Random mRandom;
- public PrivateAddressCoordinator(Context context, TetheringConfiguration config) {
+ /** Capture PrivateAddressCoordinator dependencies for injection. */
+ public static class Dependencies {
+ private final Context mContext;
+
+ Dependencies(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Check whether or not one specific experimental feature is enabled according to {@link
+ * DeviceConfigUtils}.
+ *
+ * @param featureName The feature's name to look up.
+ * @return true if this feature is enabled, or false if disabled.
+ */
+ public boolean isFeatureEnabled(String featureName) {
+ return DeviceConfigUtils.isTetheringFeatureEnabled(mContext, featureName);
+ }
+ }
+
+ public PrivateAddressCoordinator(Supplier<Network[]> getAllNetworksSupplier, Context context) {
+ this(getAllNetworksSupplier, new Dependencies(context));
+ }
+
+ @VisibleForTesting
+ public PrivateAddressCoordinator(Supplier<Network[]> getAllNetworksSupplier,
+ Dependencies deps) {
mDownstreams = new ArraySet<>();
mUpstreamPrefixMap = new ArrayMap<>();
- mConnectivityMgr = (ConnectivityManager) context.getSystemService(
- Context.CONNECTIVITY_SERVICE);
- mConfig = config;
+ mGetAllNetworksSupplier = getAllNetworksSupplier;
+ mDeps = deps;
mCachedAddresses = new ArrayMap<AddressKey, LinkAddress>();
// Reserved static addresses for bluetooth and wifi p2p.
mCachedAddresses.put(new AddressKey(TETHERING_BLUETOOTH, CONNECTIVITY_SCOPE_GLOBAL),
@@ -100,26 +133,26 @@
}
/**
- * Record a new upstream IpPrefix which may conflict with tethering downstreams.
- * The downstreams will be notified if a conflict is found. When updateUpstreamPrefix is called,
+ * Record a new upstream IpPrefix which may conflict with tethering downstreams. The downstreams
+ * will be notified if a conflict is found. When updateUpstreamPrefix is called,
* UpstreamNetworkState must have an already populated LinkProperties.
*/
- public void updateUpstreamPrefix(final UpstreamNetworkState ns) {
+ public void updateUpstreamPrefix(
+ final LinkProperties lp, final NetworkCapabilities nc, final Network network) {
// Do not support VPN as upstream. Normally, networkCapabilities is not expected to be null,
// but just checking to be sure.
- if (ns.networkCapabilities != null && ns.networkCapabilities.hasTransport(TRANSPORT_VPN)) {
- removeUpstreamPrefix(ns.network);
+ if (nc != null && nc.hasTransport(TRANSPORT_VPN)) {
+ removeUpstreamPrefix(network);
return;
}
- final ArrayList<IpPrefix> ipv4Prefixes = getIpv4Prefixes(
- ns.linkProperties.getAllLinkAddresses());
+ final ArrayList<IpPrefix> ipv4Prefixes = getIpv4Prefixes(lp.getAllLinkAddresses());
if (ipv4Prefixes.isEmpty()) {
- removeUpstreamPrefix(ns.network);
+ removeUpstreamPrefix(network);
return;
}
- mUpstreamPrefixMap.put(ns.network, ipv4Prefixes);
+ mUpstreamPrefixMap.put(network, ipv4Prefixes);
handleMaybePrefixConflict(ipv4Prefixes);
}
@@ -161,7 +194,7 @@
// Remove all upstreams that are no longer valid networks
final Set<Network> toBeRemoved = new HashSet<>(mUpstreamPrefixMap.keySet());
- toBeRemoved.removeAll(asList(mConnectivityMgr.getAllNetworks()));
+ toBeRemoved.removeAll(asList(mGetAllNetworksSupplier.get()));
mUpstreamPrefixMap.removeAll(toBeRemoved);
}
@@ -173,11 +206,6 @@
@Nullable
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,
@@ -189,7 +217,7 @@
return cachedAddress;
}
- final int prefixIndex = getStartedPrefixIndex();
+ final int prefixIndex = getRandomPrefixIndex();
for (int i = 0; i < mTetheringPrefixes.size(); i++) {
final IpPrefix prefixRange = mTetheringPrefixes.get(
(prefixIndex + i) % mTetheringPrefixes.size());
@@ -205,8 +233,8 @@
return null;
}
- private int getStartedPrefixIndex() {
- if (!mConfig.isRandomPrefixBaseEnabled()) return 0;
+ private int getRandomPrefixIndex() {
+ if (!mDeps.isFeatureEnabled(TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION)) return 0;
final int random = getRandomInt() & 0xffffff;
// This is to select the starting prefix range (/8, /12, or /16) instead of the actual
@@ -242,123 +270,62 @@
return getInUseDownstreamPrefix(prefix);
}
- // Get the next non-conflict sub prefix. E.g: To get next sub prefix from 10.0.0.0/8, if the
- // previously selected prefix is 10.20.42.0/24(subPrefix: 0.20.42.0) and the conflicting prefix
- // is 10.16.0.0/20 (10.16.0.0 ~ 10.16.15.255), then the max address under subPrefix is
- // 0.16.15.255 and the next subPrefix is 0.16.16.255/24 (0.16.15.255 + 0.0.1.0).
- // Note: the sub address 0.0.0.255 here is fine to be any value that it will be replaced as
- // selected random sub address later.
- private int getNextSubPrefix(final IpPrefix conflictPrefix, final int prefixRangeMask) {
- final int suffixMask = ~prefixLengthToV4NetmaskIntHTH(conflictPrefix.getPrefixLength());
- // The largest offset within the prefix assignment block that still conflicts with
- // conflictPrefix.
- final int maxConflict =
- (getPrefixBaseAddress(conflictPrefix) | suffixMask) & ~prefixRangeMask;
-
- final int prefixMask = prefixLengthToV4NetmaskIntHTH(PREFIX_LENGTH);
- // Pick a sub prefix a full prefix (1 << (32 - PREFIX_LENGTH) addresses) greater than
- // maxConflict. This ensures that the selected prefix never overlaps with conflictPrefix.
- // There is no need to mask the result with PREFIX_LENGTH bits because this is done by
- // findAvailablePrefixFromRange when it constructs the prefix.
- return maxConflict + (1 << (32 - PREFIX_LENGTH));
- }
-
- private LinkAddress chooseDownstreamAddress(final IpPrefix prefixRange) {
+ @VisibleForTesting
+ public LinkAddress chooseDownstreamAddress(final IpPrefix prefixRange) {
// The netmask of the prefix assignment block (e.g., 0xfff00000 for 172.16.0.0/12).
final int prefixRangeMask = prefixLengthToV4NetmaskIntHTH(prefixRange.getPrefixLength());
// The zero address in the block (e.g., 0xac100000 for 172.16.0.0/12).
final int baseAddress = getPrefixBaseAddress(prefixRange);
- // The subnet mask corresponding to PREFIX_LENGTH.
- final int prefixMask = prefixLengthToV4NetmaskIntHTH(PREFIX_LENGTH);
+ // Try to get an address within the given prefix that does not conflict with any other
+ // prefix in the system.
+ for (int i = 0; i < 20; ++i) {
+ final int randomSuffix = mRandom.nextInt() & ~prefixRangeMask;
+ final int randomAddress = baseAddress | randomSuffix;
- // The offset within prefixRange of a randomly-selected prefix of length PREFIX_LENGTH.
- // This may not be the prefix of the address returned by this method:
- // - If it is already in use, the method will return an address in another prefix.
- // - If all prefixes within prefixRange are in use, the method will return null. For
- // example, for a /24 prefix within 172.26.0.0/12, this will be a multiple of 256 in
- // [0, 1048576). In other words, a random 32-bit number with mask 0x000fff00.
- //
- // prefixRangeMask is required to ensure no wrapping. For example, consider:
- // - prefixRange 127.0.0.0/8
- // - randomPrefixStart 127.255.255.0
- // - A conflicting prefix of 127.255.254.0/23
- // In this case without prefixRangeMask, getNextSubPrefix would return 128.0.0.0, which
- // means the "start < end" check in findAvailablePrefixFromRange would not reject the prefix
- // because Java doesn't have unsigned integers, so 128.0.0.0 = 0x80000000 = -2147483648
- // is less than 127.0.0.0 = 0x7f000000 = 2130706432.
- //
- // Additionally, it makes debug output easier to read by making the numbers smaller.
- final int randomInt = getRandomInt();
- final int randomPrefixStart = randomInt & ~prefixRangeMask & prefixMask;
+ // Avoid selecting x.x.x.[0, 1, 255] addresses.
+ switch (randomAddress & 0xFF) {
+ case 0:
+ case 1:
+ case 255:
+ // Try selecting a different address
+ continue;
+ }
- // A random offset within the prefix. Used to determine the local address once the prefix
- // is selected. It does not result in an IPv4 address ending in .0, .1, or .255
- // For a PREFIX_LENGTH of 24, this is a number between 2 and 254.
- final int subAddress = getSanitizedSubAddr(randomInt, ~prefixMask);
+ // Avoid selecting commonly used subnets.
+ switch (randomAddress & 0xFFFFFF00) {
+ case 0xC0A80000: // 192.168.0.0/24
+ case 0xC0A80100: // 192.168.1.0/24
+ case 0xC0A85800: // 192.168.88.0/24
+ case 0xC0A86400: // 192.168.100.0/24
+ continue;
+ }
- // Find a prefix length PREFIX_LENGTH between randomPrefixStart and the end of the block,
- // such that the prefix does not conflict with any upstream.
- IpPrefix downstreamPrefix = findAvailablePrefixFromRange(
- randomPrefixStart, (~prefixRangeMask) + 1, baseAddress, prefixRangeMask);
- if (downstreamPrefix != null) return getLinkAddress(downstreamPrefix, subAddress);
+ // Avoid 10.0.0.0 - 10.10.255.255
+ if (randomAddress >= 0x0A000000 && randomAddress <= 0x0A0AFFFF) {
+ continue;
+ }
- // If that failed, do the same, but between 0 and randomPrefixStart.
- downstreamPrefix = findAvailablePrefixFromRange(
- 0, randomPrefixStart, baseAddress, prefixRangeMask);
-
- return getLinkAddress(downstreamPrefix, subAddress);
- }
-
- private LinkAddress getLinkAddress(final IpPrefix prefix, final int subAddress) {
- if (prefix == null) return null;
-
- final InetAddress address = intToInet4AddressHTH(getPrefixBaseAddress(prefix) | subAddress);
- return new LinkAddress(address, PREFIX_LENGTH);
- }
-
- private IpPrefix findAvailablePrefixFromRange(final int start, final int end,
- final int baseAddress, final int prefixRangeMask) {
- int newSubPrefix = start;
- while (newSubPrefix < end) {
- final InetAddress address = intToInet4AddressHTH(baseAddress | newSubPrefix);
+ final InetAddress address = intToInet4AddressHTH(randomAddress);
final IpPrefix prefix = new IpPrefix(address, PREFIX_LENGTH);
-
- final IpPrefix conflictPrefix = getConflictPrefix(prefix);
-
- if (conflictPrefix == null) return prefix;
-
- newSubPrefix = getNextSubPrefix(conflictPrefix, prefixRangeMask);
+ if (getConflictPrefix(prefix) != null) {
+ // Prefix is conflicting with another prefix used in the system, find another one.
+ continue;
+ }
+ return new LinkAddress(address, PREFIX_LENGTH);
}
-
+ // Could not find a prefix, return null and let caller try another range.
return null;
}
/** Get random int which could be used to generate random address. */
+ // TODO: get rid of this function and mock getRandomPrefixIndex in tests.
@VisibleForTesting
public int getRandomInt() {
return mRandom.nextInt();
}
- /** Get random subAddress and avoid selecting x.x.x.0, x.x.x.1 and x.x.x.255 address. */
- private int getSanitizedSubAddr(final int randomInt, final int subAddrMask) {
- final int randomSubAddr = randomInt & subAddrMask;
- // If prefix length > 30, the selecting speace would be less than 4 which may be hard to
- // avoid 3 consecutive address.
- if (PREFIX_LENGTH > 30) return randomSubAddr;
-
- // TODO: maybe it is not necessary to avoid .0, .1 and .255 address because tethering
- // address would not be conflicted. This code only works because PREFIX_LENGTH is not longer
- // than 24
- final int candidate = randomSubAddr & 0xff;
- if (candidate == 0 || candidate == 1 || candidate == 255) {
- return (randomSubAddr & 0xfffffffc) + 2;
- }
-
- return randomSubAddr;
- }
-
/** Release downstream record for IpServer. */
public void releaseDownstream(final IpServer ipServer) {
mDownstreams.remove(ipServer);
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 1938a08..13b8004 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -359,10 +359,7 @@
// Load tethering configuration.
updateConfiguration();
mConfig.readEnableSyncSM(mContext);
- // It is OK for the configuration to be passed to the PrivateAddressCoordinator at
- // construction time because the only part of the configuration it uses is
- // shouldEnableWifiP2pDedicatedIp(), and currently do not support changing that.
- mPrivateAddressCoordinator = mDeps.makePrivateAddressCoordinator(mContext, mConfig);
+ mPrivateAddressCoordinator = mDeps.makePrivateAddressCoordinator(mContext);
// Must be initialized after tethering configuration is loaded because BpfCoordinator
// constructor needs to use the configuration.
@@ -2004,7 +2001,8 @@
final UpstreamNetworkState ns = (UpstreamNetworkState) o;
switch (arg1) {
case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES:
- mPrivateAddressCoordinator.updateUpstreamPrefix(ns);
+ mPrivateAddressCoordinator.updateUpstreamPrefix(
+ ns.linkProperties, ns.networkCapabilities, ns.network);
break;
case UpstreamNetworkMonitor.EVENT_ON_LOST:
mPrivateAddressCoordinator.removeUpstreamPrefix(ns.network);
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index c9817c9..b3e9c1b 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -182,7 +182,6 @@
private final int mP2pLeasesSubnetPrefixLength;
private final boolean mEnableWearTethering;
- private final boolean mRandomPrefixBase;
private final int mUsbTetheringFunction;
protected final ContentResolver mContentResolver;
@@ -300,8 +299,6 @@
mEnableWearTethering = shouldEnableWearTethering(ctx);
- mRandomPrefixBase = mDeps.isFeatureEnabled(ctx, TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION);
-
configLog.log(toString());
}
@@ -390,10 +387,6 @@
return mEnableWearTethering;
}
- public boolean isRandomPrefixBaseEnabled() {
- return mRandomPrefixBase;
- }
-
/**
* Check whether sync SM is enabled then set it to USE_SYNC_SM. This should be called once
* when tethering is created. Otherwise if the flag is pushed while tethering is enabled,
@@ -455,9 +448,6 @@
pw.print("mUsbTetheringFunction: ");
pw.println(isUsingNcm() ? "NCM" : "RNDIS");
- pw.print("mRandomPrefixBase: ");
- pw.println(mRandomPrefixBase);
-
pw.print("USE_SYNC_SM: ");
pw.println(USE_SYNC_SM);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 5d9d349..cc878d5 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -21,6 +21,7 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.connectivity.ConnectivityInternalApiUtil;
import android.net.ip.IpServer;
@@ -176,9 +177,9 @@
/**
* Make PrivateAddressCoordinator to be used by Tethering.
*/
- public PrivateAddressCoordinator makePrivateAddressCoordinator(Context ctx,
- TetheringConfiguration cfg) {
- return new PrivateAddressCoordinator(ctx, cfg);
+ public PrivateAddressCoordinator makePrivateAddressCoordinator(Context ctx) {
+ final ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class);
+ return new PrivateAddressCoordinator(cm::getAllNetworks, ctx);
}
/**
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
index fc50faf..a744953 100644
--- a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
@@ -62,7 +62,6 @@
import android.net.NetworkTemplate;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.Looper;
import android.stats.connectivity.DownstreamType;
import android.stats.connectivity.ErrorCode;
import android.stats.connectivity.UpstreamType;
@@ -111,7 +110,11 @@
private final SparseArray<NetworkTetheringReported.Builder> mBuilderMap = new SparseArray<>();
private final SparseArray<Long> mDownstreamStartTime = new SparseArray<Long>();
private final ArrayList<RecordUpstreamEvent> mUpstreamEventList = new ArrayList<>();
- private final ArrayMap<UpstreamType, DataUsage> mUpstreamUsageBaseline = new ArrayMap<>();
+ // Store the last reported data usage for each upstream type to be used for calculating the
+ // usage delta. The keys are the upstream types, and the values are the tethering UID data
+ // usage for the corresponding types. Retrieve the baseline data usage when tethering is
+ // enabled, update it when the upstream changes, and clear it when tethering is disabled.
+ private final ArrayMap<UpstreamType, DataUsage> mLastReportedUpstreamUsage = new ArrayMap<>();
private final Context mContext;
private final Dependencies mDependencies;
private final NetworkStatsManager mNetworkStatsManager;
@@ -157,10 +160,15 @@
/**
* @see Handler
+ *
+ * Note: This should only be called once, within the constructor, as it creates a new
+ * thread. Calling it multiple times could lead to a thread leak.
*/
@NonNull
- public Handler createHandler(Looper looper) {
- return new Handler(looper);
+ public Handler createHandler() {
+ final HandlerThread thread = new HandlerThread(TAG);
+ thread.start();
+ return new Handler(thread.getLooper());
}
}
@@ -177,9 +185,7 @@
mContext = context;
mDependencies = dependencies;
mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
- final HandlerThread thread = new HandlerThread(TAG);
- thread.start();
- mHandler = dependencies.createHandler(thread.getLooper());
+ mHandler = dependencies.createHandler();
}
@VisibleForTesting
@@ -282,22 +288,33 @@
* Calculates the data usage difference between the current and previous usage for the
* specified upstream type.
*
+ * Note: This must be called before updating mCurrentUpstream when changing the upstream.
+ *
* @return A DataUsage object containing the calculated difference in transmitted (tx) and
* received (rx) bytes.
*/
private DataUsage calculateDataUsageDelta(@Nullable UpstreamType upstream) {
- if (upstream != null && mDependencies.isUpstreamDataUsageMetricsEnabled(mContext)
- && isUsageSupportedForUpstreamType(upstream)) {
- final DataUsage oldUsage = mUpstreamUsageBaseline.getOrDefault(upstream, EMPTY);
- if (oldUsage.equals(EMPTY)) {
- Log.d(TAG, "No usage baseline for the upstream=" + upstream);
- return EMPTY;
- }
- // TODO(b/352537247): Fix data usage which might be incorrect if the device uses
- // tethering with the same upstream for over 15 days.
- return DataUsage.subtract(getCurrentDataUsageForUpstreamType(upstream), oldUsage);
+ if (!mDependencies.isUpstreamDataUsageMetricsEnabled(mContext)) {
+ return EMPTY;
}
- return EMPTY;
+
+ if (upstream == null || !isUsageSupportedForUpstreamType(upstream)) {
+ return EMPTY;
+ }
+
+ final DataUsage oldUsage = mLastReportedUpstreamUsage.getOrDefault(upstream, EMPTY);
+ if (oldUsage.equals(EMPTY)) {
+ Log.d(TAG, "No usage baseline for the upstream=" + upstream);
+ return EMPTY;
+ }
+ // TODO(b/370724247): Fix data usage which might be incorrect if the device uses
+ // tethering with the same upstream for over 15 days.
+ // Need to refresh the baseline usage data. If the network switches back to Wi-Fi after
+ // using cellular data (Wi-Fi -> Cellular -> Wi-Fi), the old baseline might be
+ // inaccurate, leading to incorrect delta calculations.
+ final DataUsage newUsage = getCurrentDataUsageForUpstreamType(upstream);
+ mLastReportedUpstreamUsage.put(upstream, newUsage);
+ return DataUsage.subtract(newUsage, oldUsage);
}
/**
@@ -416,7 +433,6 @@
* @param reported a NetworkTetheringReported object containing statistics to write
*/
private void write(@NonNull final NetworkTetheringReported reported) {
- final byte[] upstreamEvents = reported.getUpstreamEvents().toByteArray();
mDependencies.write(reported);
if (DBG) {
Log.d(
@@ -430,7 +446,7 @@
+ ", userType: "
+ reported.getUserType().getNumber()
+ ", upstreamTypes: "
- + Arrays.toString(upstreamEvents)
+ + Arrays.toString(reported.getUpstreamEvents().toByteArray())
+ ", durationMillis: "
+ reported.getDurationMillis());
}
@@ -444,25 +460,29 @@
}
private void handleInitUpstreamUsageBaseline() {
- if (!(mDependencies.isUpstreamDataUsageMetricsEnabled(mContext)
- && mUpstreamUsageBaseline.isEmpty())) {
+ if (!mDependencies.isUpstreamDataUsageMetricsEnabled(mContext)) {
+ return;
+ }
+
+ if (!mLastReportedUpstreamUsage.isEmpty()) {
+ Log.wtf(TAG, "The upstream usage baseline has been initialed.");
return;
}
for (UpstreamType type : UpstreamType.values()) {
if (!isUsageSupportedForUpstreamType(type)) continue;
- mUpstreamUsageBaseline.put(type, getCurrentDataUsageForUpstreamType(type));
+ mLastReportedUpstreamUsage.put(type, getCurrentDataUsageForUpstreamType(type));
}
}
@VisibleForTesting
@NonNull
- DataUsage getDataUsageFromUpstreamType(@NonNull UpstreamType type) {
+ DataUsage getLastReportedUsageFromUpstreamType(@NonNull UpstreamType type) {
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
throw new IllegalStateException(
"Not running on Handler thread: " + Thread.currentThread().getName());
}
- return mUpstreamUsageBaseline.getOrDefault(type, EMPTY);
+ return mLastReportedUpstreamUsage.getOrDefault(type, EMPTY);
}
@@ -497,7 +517,7 @@
mUpstreamEventList.clear();
mCurrentUpstream = null;
mCurrentUpStreamStartTime = 0L;
- mUpstreamUsageBaseline.clear();
+ mLastReportedUpstreamUsage.clear();
}
private DownstreamType downstreamTypeToEnum(final int ifaceType) {
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 423b9b8..01f3af9 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -70,7 +70,7 @@
import com.android.net.module.util.structs.FragmentHeader;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.testutils.HandlerUtils;
-import com.android.testutils.TapPacketReader;
+import com.android.testutils.PollPacketReader;
import com.android.testutils.TestNetworkTracker;
import org.junit.After;
@@ -158,10 +158,10 @@
protected TetheredInterfaceRequester mTetheredInterfaceRequester;
// Late initialization in initTetheringTester().
- private TapPacketReader mUpstreamReader;
+ private PollPacketReader mUpstreamReader;
private TestNetworkTracker mUpstreamTracker;
private TestNetworkInterface mDownstreamIface;
- private TapPacketReader mDownstreamReader;
+ private PollPacketReader mDownstreamReader;
private MyTetheringEventCallback mTetheringEventCallback;
public Context getContext() {
@@ -187,10 +187,10 @@
return runAsShell(NETWORK_SETTINGS, TETHER_PRIVILEGED, () -> sTm.isTetheringSupported());
}
- protected void maybeStopTapPacketReader(final TapPacketReader tapPacketReader)
+ protected void maybeStopTapPacketReader(final PollPacketReader tapPacketReader)
throws Exception {
if (tapPacketReader != null) {
- TapPacketReader reader = tapPacketReader;
+ PollPacketReader reader = tapPacketReader;
mHandler.post(() -> reader.stop());
}
}
@@ -228,7 +228,7 @@
});
}
if (mUpstreamReader != null) {
- TapPacketReader reader = mUpstreamReader;
+ PollPacketReader reader = mUpstreamReader;
mHandler.post(() -> reader.stop());
mUpstreamReader = null;
}
@@ -291,7 +291,7 @@
});
}
- protected static void waitForRouterAdvertisement(TapPacketReader reader, String iface,
+ protected static void waitForRouterAdvertisement(PollPacketReader reader, String iface,
long timeoutMs) {
final long deadline = SystemClock.uptimeMillis() + timeoutMs;
do {
@@ -574,13 +574,13 @@
return nif.getIndex();
}
- protected TapPacketReader makePacketReader(final TestNetworkInterface iface) throws Exception {
+ protected PollPacketReader makePacketReader(final TestNetworkInterface iface) throws Exception {
FileDescriptor fd = iface.getFileDescriptor().getFileDescriptor();
return makePacketReader(fd, getMTU(iface));
}
- protected TapPacketReader makePacketReader(FileDescriptor fd, int mtu) {
- final TapPacketReader reader = new TapPacketReader(mHandler, fd, mtu);
+ protected PollPacketReader makePacketReader(FileDescriptor fd, int mtu) {
+ final PollPacketReader reader = new PollPacketReader(mHandler, fd, mtu);
mHandler.post(() -> reader.start());
HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
return reader;
diff --git a/Tethering/tests/integration/base/android/net/TetheringTester.java b/Tethering/tests/integration/base/android/net/TetheringTester.java
index b152b4c..fb94eed 100644
--- a/Tethering/tests/integration/base/android/net/TetheringTester.java
+++ b/Tethering/tests/integration/base/android/net/TetheringTester.java
@@ -84,7 +84,7 @@
import com.android.net.module.util.structs.RaHeader;
import com.android.net.module.util.structs.TcpHeader;
import com.android.net.module.util.structs.UdpHeader;
-import com.android.testutils.TapPacketReader;
+import com.android.testutils.PollPacketReader;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -157,14 +157,14 @@
public static final String DHCP_HOSTNAME = "testhostname";
private final ArrayMap<MacAddress, TetheredDevice> mTetheredDevices;
- private final TapPacketReader mDownstreamReader;
- private final TapPacketReader mUpstreamReader;
+ private final PollPacketReader mDownstreamReader;
+ private final PollPacketReader mUpstreamReader;
- public TetheringTester(TapPacketReader downstream) {
+ public TetheringTester(PollPacketReader downstream) {
this(downstream, null);
}
- public TetheringTester(TapPacketReader downstream, TapPacketReader upstream) {
+ public TetheringTester(PollPacketReader downstream, PollPacketReader upstream) {
if (downstream == null) fail("Downstream reader could not be NULL");
mDownstreamReader = downstream;
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 32b2f3e..1bbea94 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -80,7 +80,7 @@
import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.NetworkStackModuleTest;
-import com.android.testutils.TapPacketReader;
+import com.android.testutils.PollPacketReader;
import org.junit.After;
import org.junit.Rule;
@@ -213,7 +213,7 @@
TestNetworkInterface downstreamIface = null;
MyTetheringEventCallback tetheringEventCallback = null;
- TapPacketReader downstreamReader = null;
+ PollPacketReader downstreamReader = null;
try {
downstreamIface = createTestInterface();
@@ -253,7 +253,7 @@
TestNetworkInterface downstreamIface = null;
MyTetheringEventCallback tetheringEventCallback = null;
- TapPacketReader downstreamReader = null;
+ PollPacketReader downstreamReader = null;
try {
downstreamIface = createTestInterface();
@@ -283,7 +283,7 @@
TestNetworkInterface downstreamIface = null;
MyTetheringEventCallback tetheringEventCallback = null;
- TapPacketReader downstreamReader = null;
+ PollPacketReader downstreamReader = null;
try {
downstreamIface = createTestInterface();
@@ -357,7 +357,7 @@
TestNetworkInterface downstreamIface = null;
MyTetheringEventCallback tetheringEventCallback = null;
- TapPacketReader downstreamReader = null;
+ PollPacketReader downstreamReader = null;
try {
downstreamIface = createTestInterface();
@@ -423,7 +423,7 @@
// client, which is not possible in this test.
}
- private void checkTetheredClientCallbacks(final TapPacketReader packetReader,
+ private void checkTetheredClientCallbacks(final PollPacketReader packetReader,
final MyTetheringEventCallback tetheringEventCallback) throws Exception {
// Create a fake client.
byte[] clientMacAddr = new byte[6];
diff --git a/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java b/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java
index ebf09ed..0f3f5bb 100644
--- a/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java
+++ b/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java
@@ -43,7 +43,7 @@
import com.android.networkstack.tethering.util.TetheringUtils;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
-import com.android.testutils.TapPacketReader;
+import com.android.testutils.PollPacketReader;
import com.android.testutils.TapPacketReaderRule;
import org.junit.After;
@@ -75,7 +75,7 @@
private InterfaceParams mUpstreamParams, mTetheredParams;
private HandlerThread mHandlerThread;
private Handler mHandler;
- private TapPacketReader mUpstreamPacketReader, mTetheredPacketReader;
+ private PollPacketReader mUpstreamPacketReader, mTetheredPacketReader;
private static INetd sNetd;
@@ -219,7 +219,7 @@
}
// TODO: change to assert.
- private boolean waitForPacket(ByteBuffer packet, TapPacketReader reader) {
+ private boolean waitForPacket(ByteBuffer packet, PollPacketReader reader) {
byte[] p;
while ((p = reader.popPacket(PACKET_TIMEOUT_MS)) != null) {
@@ -247,7 +247,7 @@
}
private void receivePacketAndMaybeExpectForwarded(boolean expectForwarded,
- ByteBuffer in, TapPacketReader inReader, ByteBuffer out, TapPacketReader outReader)
+ ByteBuffer in, PollPacketReader inReader, ByteBuffer out, PollPacketReader outReader)
throws IOException {
inReader.sendResponse(in);
@@ -271,13 +271,13 @@
assertEquals(msg, expectForwarded, waitForPacket(out, outReader));
}
- private void receivePacketAndExpectForwarded(ByteBuffer in, TapPacketReader inReader,
- ByteBuffer out, TapPacketReader outReader) throws IOException {
+ private void receivePacketAndExpectForwarded(ByteBuffer in, PollPacketReader inReader,
+ ByteBuffer out, PollPacketReader outReader) throws IOException {
receivePacketAndMaybeExpectForwarded(true, in, inReader, out, outReader);
}
- private void receivePacketAndExpectNotForwarded(ByteBuffer in, TapPacketReader inReader,
- ByteBuffer out, TapPacketReader outReader) throws IOException {
+ private void receivePacketAndExpectNotForwarded(ByteBuffer in, PollPacketReader inReader,
+ ByteBuffer out, PollPacketReader outReader) throws IOException {
receivePacketAndMaybeExpectForwarded(false, in, inReader, out, outReader);
}
diff --git a/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java b/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java
index 90ceaa1..7cc8c74 100644
--- a/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java
+++ b/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java
@@ -64,7 +64,7 @@
import com.android.net.module.util.structs.PrefixInformationOption;
import com.android.net.module.util.structs.RaHeader;
import com.android.net.module.util.structs.RdnssOption;
-import com.android.testutils.TapPacketReader;
+import com.android.testutils.PollPacketReader;
import com.android.testutils.TapPacketReaderRule;
import org.junit.After;
@@ -93,7 +93,7 @@
private InterfaceParams mTetheredParams;
private HandlerThread mHandlerThread;
private Handler mHandler;
- private TapPacketReader mTetheredPacketReader;
+ private PollPacketReader mTetheredPacketReader;
private RouterAdvertisementDaemon mRaDaemon;
private static INetd sNetd;
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 177296a..f7834a3 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -139,6 +139,7 @@
private static final boolean DEFAULT_USING_BPF_OFFLOAD = true;
private static final int DEFAULT_SUBNET_PREFIX_LENGTH = 0;
private static final int P2P_SUBNET_PREFIX_LENGTH = 25;
+ private static final String LEGACY_WIFI_P2P_IFACE_ADDRESS = "192.168.49.1/24";
private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
@@ -196,6 +197,12 @@
private void initStateMachine(int interfaceType, boolean usingLegacyDhcp,
boolean usingBpfOffload) throws Exception {
+ initStateMachine(interfaceType, usingLegacyDhcp, usingBpfOffload,
+ false /* shouldEnableWifiP2pDedicatedIp */);
+ }
+
+ private void initStateMachine(int interfaceType, boolean usingLegacyDhcp,
+ boolean usingBpfOffload, boolean shouldEnableWifiP2pDedicatedIp) throws Exception {
when(mDependencies.getDadProxy(any(), any())).thenReturn(mDadProxy);
when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
@@ -213,6 +220,8 @@
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(usingBpfOffload);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(usingLegacyDhcp);
when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH);
+ when(mTetherConfig.shouldEnableWifiP2pDedicatedIp())
+ .thenReturn(shouldEnableWifiP2pDedicatedIp);
when(mBpfCoordinator.isUsingBpfOffload()).thenReturn(usingBpfOffload);
mIpServer = createIpServer(interfaceType);
mIpServer.start();
@@ -409,7 +418,7 @@
}
@Test
- public void canBeTetheredAsWifiP2p() throws Exception {
+ public void canBeTetheredAsWifiP2p_NotUsingDedicatedIp() throws Exception {
initStateMachine(TETHERING_WIFI_P2P);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
@@ -431,6 +440,33 @@
}
@Test
+ public void canBeTetheredAsWifiP2p_UsingDedicatedIp() throws Exception {
+ initStateMachine(TETHERING_WIFI_P2P, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD,
+ true /* shouldEnableWifiP2pDedicatedIp */);
+
+ dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
+ InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
+ // When using WiFi P2p dedicated IP, the IpServer just picks the IP address without
+ // requesting for it at PrivateAddressCoordinator.
+ inOrder.verify(mAddressCoordinator, never()).requestDownstreamAddress(any(), anyInt(),
+ anyBoolean());
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+ IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP)));
+ inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+ inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+ inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
+ any(), any());
+ inOrder.verify(mCallback).updateInterfaceState(
+ mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR);
+ inOrder.verify(mCallback).updateLinkProperties(
+ eq(mIpServer), mLinkPropertiesCaptor.capture());
+ assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
+ assertEquals(List.of(new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS)),
+ mLinkPropertiesCaptor.getValue().getLinkAddresses());
+ verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
+ }
+
+ @Test
public void handlesFirstUpstreamChange() throws Exception {
initTetheredStateMachine(TETHERING_BLUETOOTH, null);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 5d22977..dd10cc3 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -2032,7 +2032,7 @@
final BpfCoordinator coordinator = makeBpfCoordinator();
initBpfCoordinatorForRule4(coordinator);
resetNetdAndBpfMaps();
- assertEquals(0, mConsumer.getLastMaxConnectionAndResetToCurrent());
+ assertEquals(0, coordinator.getLastMaxConnectionAndResetToCurrent());
// Prepare add/delete rule events.
final ArrayList<ConntrackEvent> addRuleEvents = new ArrayList<>();
@@ -2049,49 +2049,44 @@
// Add rules, verify counter increases.
for (int i = 0; i < 5; i++) {
mConsumer.accept(addRuleEvents.get(i));
- assertConsumerCountersEquals(supportActiveSessionsMetrics ? i + 1 : 0);
+ assertEquals(supportActiveSessionsMetrics ? i + 1 : 0,
+ coordinator.getLastMaxConnectionAndResetToCurrent());
}
// Add the same events again should not increase the counter because
// all events are already exist.
for (final ConntrackEvent event : addRuleEvents) {
mConsumer.accept(event);
- assertConsumerCountersEquals(supportActiveSessionsMetrics ? 5 : 0);
+ assertEquals(supportActiveSessionsMetrics ? 5 : 0,
+ coordinator.getLastMaxConnectionAndResetToCurrent());
}
// Verify removing non-existent items won't change the counters.
for (int i = 5; i < 8; i++) {
mConsumer.accept(new TestConntrackEvent.Builder().setMsgType(
IPCTNL_MSG_CT_DELETE).setProto(IPPROTO_TCP).setRemotePort(i).build());
- assertConsumerCountersEquals(supportActiveSessionsMetrics ? 5 : 0);
+ assertEquals(supportActiveSessionsMetrics ? 5 : 0,
+ coordinator.getLastMaxConnectionAndResetToCurrent());
}
// Verify remove the rules decrease the counter.
// Note the max counter returns the max, so it returns the count before deleting.
for (int i = 0; i < 5; i++) {
mConsumer.accept(delRuleEvents.get(i));
- assertEquals(supportActiveSessionsMetrics ? 4 - i : 0,
- mConsumer.getCurrentConnectionCount());
- assertEquals(supportActiveSessionsMetrics ? 5 - i : 0,
- mConsumer.getLastMaxConnectionCount());
- assertEquals(supportActiveSessionsMetrics ? 5 - i : 0,
- mConsumer.getLastMaxConnectionAndResetToCurrent());
}
+ // The maximum number of rules observed is still 5.
+ assertEquals(supportActiveSessionsMetrics ? 5 : 0,
+ coordinator.getLastMaxConnectionAndResetToCurrent());
+ // After the reset, the maximum number of rules observed is 0.
+ assertEquals(0, coordinator.getLastMaxConnectionAndResetToCurrent());
// Verify remove these rules again doesn't decrease the counter.
for (int i = 0; i < 5; i++) {
mConsumer.accept(delRuleEvents.get(i));
- assertConsumerCountersEquals(0);
+ assertEquals(0, coordinator.getLastMaxConnectionAndResetToCurrent());
}
}
- // Helper method to assert all counter values inside consumer.
- private void assertConsumerCountersEquals(int expectedCount) {
- assertEquals(expectedCount, mConsumer.getCurrentConnectionCount());
- assertEquals(expectedCount, mConsumer.getLastMaxConnectionCount());
- assertEquals(expectedCount, mConsumer.getLastMaxConnectionAndResetToCurrent());
- }
-
@FeatureFlag(name = TETHER_ACTIVE_SESSIONS_METRICS)
// BPF IPv4 forwarding only supports on S+.
@IgnoreUpTo(Build.VERSION_CODES.R)
@@ -2121,7 +2116,7 @@
coordinator.tetherOffloadClientAdd(mIpServer, clientB);
assertClientInfoExists(mIpServer, clientA);
assertClientInfoExists(mIpServer, clientB);
- assertEquals(0, mConsumer.getLastMaxConnectionAndResetToCurrent());
+ assertEquals(0, coordinator.getLastMaxConnectionAndResetToCurrent());
// Add some rules for both clients.
final int addr1RuleCount = 5;
@@ -2145,31 +2140,24 @@
.build());
}
- assertConsumerCountersEquals(
- supportActiveSessionsMetrics ? addr1RuleCount + addr2RuleCount : 0);
+ assertEquals(supportActiveSessionsMetrics ? addr1RuleCount + addr2RuleCount : 0,
+ coordinator.getLastMaxConnectionAndResetToCurrent());
// Remove 1 client. Since the 1st poll will return the LastMaxCounter and
- // update it to the current, the max counter will be kept at 1st poll, while
- // the current counter reflect the rule decreasing.
+ // update it to the current, the max counter will be kept at 1st poll.
coordinator.tetherOffloadClientRemove(mIpServer, clientA);
+ assertEquals(supportActiveSessionsMetrics ? addr1RuleCount + addr2RuleCount : 0,
+ coordinator.getLastMaxConnectionAndResetToCurrent());
+ // And the counter be updated at 2nd poll.
assertEquals(supportActiveSessionsMetrics ? addr2RuleCount : 0,
- mConsumer.getCurrentConnectionCount());
- assertEquals(supportActiveSessionsMetrics ? addr1RuleCount + addr2RuleCount : 0,
- mConsumer.getLastMaxConnectionCount());
- assertEquals(supportActiveSessionsMetrics ? addr1RuleCount + addr2RuleCount : 0,
- mConsumer.getLastMaxConnectionAndResetToCurrent());
- // And all counters be updated at 2nd poll.
- assertConsumerCountersEquals(supportActiveSessionsMetrics ? addr2RuleCount : 0);
+ coordinator.getLastMaxConnectionAndResetToCurrent());
// Remove other client.
coordinator.tetherOffloadClientRemove(mIpServer, clientB);
- assertEquals(0, mConsumer.getCurrentConnectionCount());
assertEquals(supportActiveSessionsMetrics ? addr2RuleCount : 0,
- mConsumer.getLastMaxConnectionCount());
- assertEquals(supportActiveSessionsMetrics ? addr2RuleCount : 0,
- mConsumer.getLastMaxConnectionAndResetToCurrent());
- // All counters reach zero at 2nd poll.
- assertConsumerCountersEquals(0);
+ coordinator.getLastMaxConnectionAndResetToCurrent());
+ // Verify the counter reach zero at 2nd poll.
+ assertEquals(0, coordinator.getLastMaxConnectionAndResetToCurrent());
}
@FeatureFlag(name = TETHER_ACTIVE_SESSIONS_METRICS)
@@ -2191,7 +2179,7 @@
final BpfCoordinator coordinator = makeBpfCoordinator();
initBpfCoordinatorForRule4(coordinator);
resetNetdAndBpfMaps();
- assertConsumerCountersEquals(0);
+ assertEquals(0, coordinator.getLastMaxConnectionAndResetToCurrent());
// Prepare the counter value.
for (int i = 0; i < 5; i++) {
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 2298a1a..bff1fda 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -16,7 +16,6 @@
package com.android.networkstack.tethering;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-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;
@@ -26,12 +25,15 @@
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static com.android.networkstack.tethering.PrivateAddressCoordinator.TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION;
import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -70,7 +72,7 @@
@Mock private IpServer mWifiP2pIpServer;
@Mock private Context mContext;
@Mock private ConnectivityManager mConnectivityMgr;
- @Mock private TetheringConfiguration mConfig;
+ @Mock private PrivateAddressCoordinator.Dependencies mDeps;
private PrivateAddressCoordinator mPrivateAddressCoordinator;
private final LinkAddress mBluetoothAddress = new LinkAddress("192.168.44.1/24");
@@ -103,11 +105,11 @@
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mConnectivityMgr);
+ when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityMgr);
when(mConnectivityMgr.getAllNetworks()).thenReturn(mAllNetworks);
- when(mConfig.shouldEnableWifiP2pDedicatedIp()).thenReturn(false);
- when(mConfig.isRandomPrefixBaseEnabled()).thenReturn(false);
setUpIpServers();
- mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig));
+ mPrivateAddressCoordinator =
+ spy(new PrivateAddressCoordinator(mConnectivityMgr::getAllNetworks, mDeps));
}
private LinkAddress requestDownstreamAddress(final IpServer ipServer, int scope,
@@ -118,6 +120,11 @@
return address;
}
+ private void updateUpstreamPrefix(UpstreamNetworkState ns) {
+ mPrivateAddressCoordinator.updateUpstreamPrefix(
+ ns.linkProperties, ns.networkCapabilities, ns.network);
+ }
+
@Test
public void testRequestDownstreamAddressWithoutUsingLastAddress() throws Exception {
final IpPrefix bluetoothPrefix = asIpPrefix(mBluetoothAddress);
@@ -143,37 +150,6 @@
}
@Test
- public void testSanitizedAddress() throws Exception {
- int fakeSubAddr = 0x2b00; // 43.0.
- when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
- LinkAddress actualAddress = requestDownstreamAddress(mHotspotIpServer,
- 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,
- 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,
- 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,
- CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
- assertEquals(new LinkAddress("192.168.47.5/24"), actualAddress);
- mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
- }
-
- @Test
public void testReservedPrefix() throws Exception {
// - Test bluetooth prefix is reserved.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
@@ -208,22 +184,15 @@
@Test
public void testRequestLastDownstreamAddress() throws Exception {
- final int fakeHotspotSubAddr = 0x2b05; // 43.5
- when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
- assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.43.5/24"), hotspotAddress);
final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
- assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.45.5/24"), usbAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
- final int newFakeSubAddr = 0x3c05;
- when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
-
final LinkAddress newHotspotAddress = requestDownstreamAddress(mHotspotIpServer,
CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
assertEquals(hotspotAddress, newHotspotAddress);
@@ -234,7 +203,7 @@
final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
new LinkAddress("192.168.88.23/16"), null,
makeNetworkCapabilities(TRANSPORT_WIFI));
- mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
+ updateUpstreamPrefix(wifiUpstream);
verify(mHotspotIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
verify(mUsbIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
}
@@ -261,262 +230,27 @@
}
@Test
- public void testNoConflictUpstreamPrefix() throws Exception {
- final int fakeHotspotSubAddr = 0x2b05; // 43.5
- final IpPrefix predefinedPrefix = new IpPrefix("192.168.43.0/24");
- // Force always get subAddress "43.5" for conflict testing.
- when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
- // - Enable hotspot with prefix 192.168.43.0/24
- final LinkAddress hotspotAddr = requestDownstreamAddress(mHotspotIpServer,
- 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
- // because NetworkCapabilities update should always happen before LinkProperties update
- // and the UpstreamNetworkState update, just make sure no crash in this case.
- final UpstreamNetworkState noCapUpstream = buildUpstreamNetworkState(mMobileNetwork,
- new LinkAddress("10.0.0.8/24"), null, null);
- mPrivateAddressCoordinator.updateUpstreamPrefix(noCapUpstream);
- verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
- // - test mobile upstream with no address.
- final UpstreamNetworkState noAddress = buildUpstreamNetworkState(mMobileNetwork,
- null, null, makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(noCapUpstream);
- verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
- // - Update v6 only mobile network, hotspot prefix should not be removed.
- final UpstreamNetworkState v6OnlyMobile = buildUpstreamNetworkState(mMobileNetwork,
- null, new LinkAddress("2001:db8::/64"),
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(v6OnlyMobile);
- verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
- mPrivateAddressCoordinator.removeUpstreamPrefix(mMobileNetwork);
- // - Update v4 only mobile network, hotspot prefix should not be removed.
- final UpstreamNetworkState v4OnlyMobile = buildUpstreamNetworkState(mMobileNetwork,
- new LinkAddress("10.0.0.8/24"), null,
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyMobile);
- verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
- // - Update v4v6 mobile network, hotspot prefix should not be removed.
- final UpstreamNetworkState v4v6Mobile = buildUpstreamNetworkState(mMobileNetwork,
- new LinkAddress("10.0.0.8/24"), new LinkAddress("2001:db8::/64"),
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(v4v6Mobile);
- verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
- // - Update v6 only wifi network, hotspot prefix should not be removed.
- final UpstreamNetworkState v6OnlyWifi = buildUpstreamNetworkState(mWifiNetwork,
- null, new LinkAddress("2001:db8::/64"), makeNetworkCapabilities(TRANSPORT_WIFI));
- mPrivateAddressCoordinator.updateUpstreamPrefix(v6OnlyWifi);
- verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
- mPrivateAddressCoordinator.removeUpstreamPrefix(mWifiNetwork);
- // - Update vpn network, it conflict with hotspot prefix but VPN networks are ignored.
- final UpstreamNetworkState v4OnlyVpn = buildUpstreamNetworkState(mVpnNetwork,
- new LinkAddress("192.168.43.5/24"), null, makeNetworkCapabilities(TRANSPORT_VPN));
- mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyVpn);
- verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
- // - Update v4 only wifi network, it conflict with hotspot prefix.
- final UpstreamNetworkState v4OnlyWifi = buildUpstreamNetworkState(mWifiNetwork,
- new LinkAddress("192.168.43.5/24"), null, makeNetworkCapabilities(TRANSPORT_WIFI));
- mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyWifi);
- verify(mHotspotIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
- reset(mHotspotIpServer);
- // - Restart hotspot again and its prefix is different previous.
- mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
- final LinkAddress hotspotAddr2 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- 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,
- CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
- final IpPrefix ethPrefix = asIpPrefix(ethAddr);
- assertEquals(predefinedPrefix, ethPrefix);
+ public void testChooseDownstreamAddress_noUpstreamConflicts() throws Exception {
+ LinkAddress address = new LinkAddress("192.168.42.42/24");
+ UpstreamNetworkState ns = buildUpstreamNetworkState(mMobileNetwork, address, null, null);
+ updateUpstreamPrefix(ns);
+ // try to look for a /24 in upstream that does not conflict with upstream -> impossible.
+ assertNull(mPrivateAddressCoordinator.chooseDownstreamAddress(asIpPrefix(address)));
+
+ IpPrefix prefix = new IpPrefix("192.168.0.0/16");
+ LinkAddress chosenAddress = mPrivateAddressCoordinator.chooseDownstreamAddress(prefix);
+ assertNotNull(chosenAddress);
+ assertTrue(prefix.containsPrefix(asIpPrefix(chosenAddress)));
}
@Test
- public void testChooseAvailablePrefix() throws Exception {
- final int randomAddress = 0x8605; // 134.5
- when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
- final LinkAddress addr0 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- new LinkAddress("192.168.134.13/26"), null,
- makeNetworkCapabilities(TRANSPORT_WIFI));
- mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
-
- // Check whether return address is next prefix of 192.168.134.0/24.
- final LinkAddress addr1 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- makeNetworkCapabilities(TRANSPORT_WIFI));
- mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream2);
-
-
- // The conflict range is 128 ~ 159, so the address is 192.168.160.5/24.
- final LinkAddress addr2 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- // Update another conflict upstream which is covered by the previous one (but not the first
- // one) and verify whether this would affect the result.
- final UpstreamNetworkState mobileUpstream2 = buildUpstreamNetworkState(mMobileNetwork2,
- new LinkAddress("192.168.170.7/19"), null,
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream);
- mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream2);
-
- // The conflict range are 128 ~ 159 and 159 ~ 191, so the address is 192.168.192.5/24.
- final LinkAddress addr3 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream3);
-
- // 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,
- 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,
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream4);
-
- // Conflict ranges: 128 ~ 255 and 0 ~ 7, so the address is 192.168.8.5/24.
- final LinkAddress addr5 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream5);
-
- // Update an upstream that does *not* conflict, check whether return the same address
- // 192.168.5/24.
- final LinkAddress addr6 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream6);
-
- // Conflict ranges: 0 ~ 15 and 128 ~ 255, so the address is 192.168.16.5/24.
- final LinkAddress addr7 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream7);
-
- // 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,
- CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
- assertEquals("Wrong prefix: ", new LinkAddress("172.16.134.5/24"), addr8);
- }
-
- @Test
- public void testChoosePrefixFromDifferentRanges() throws Exception {
- final int randomAddress = 0x1f2b2a; // 31.43.42
- when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
- final LinkAddress classC1 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- new LinkAddress("192.168.88.23/17"), null,
- makeNetworkCapabilities(TRANSPORT_WIFI));
- mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
- verifyNotifyConflictAndRelease(mHotspotIpServer);
-
- // Check whether return address is next address of prefix 192.168.128.0/17.
- final LinkAddress classC2 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream);
- verifyNotifyConflictAndRelease(mHotspotIpServer);
-
- // Check whether return address is under prefix 172.16.0.0/12.
- final LinkAddress classB1 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream2);
- verifyNotifyConflictAndRelease(mHotspotIpServer);
-
- // 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,
- 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,
- 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,
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream3);
- verifyNotifyConflictAndRelease(mHotspotIpServer);
- verify(mUsbIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
-
- // Check whether return address is next address of prefix 172.16.1.42/24.
- final LinkAddress classB4 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream4);
- verifyNotifyConflictAndRelease(mHotspotIpServer);
- verifyNotifyConflictAndRelease(mUsbIpServer);
-
- // Check whether return address is next address of prefix 172.16.0.1/13.
- final LinkAddress classB5 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- 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,
- makeNetworkCapabilities(TRANSPORT_CELLULAR));
- mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream5);
- verifyNotifyConflictAndRelease(mHotspotIpServer);
- verifyNotifyConflictAndRelease(mUsbIpServer);
-
- // Check whether return address is prefix 10.0.0.0/8 + subAddress 0.31.43.42.
- final LinkAddress classA1 = requestDownstreamAddress(mHotspotIpServer,
- 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,
- CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
- assertEquals("Wrong prefix: ", new LinkAddress("10.31.44.42/24"), classA2);
+ public void testChooseDownstreamAddress_excludesWellKnownPrefixes() throws Exception {
+ IpPrefix prefix = new IpPrefix("192.168.0.0/24");
+ assertNull(mPrivateAddressCoordinator.chooseDownstreamAddress(prefix));
+ prefix = new IpPrefix("192.168.100.0/24");
+ assertNull(mPrivateAddressCoordinator.chooseDownstreamAddress(prefix));
+ prefix = new IpPrefix("10.3.0.0/16");
+ assertNull(mPrivateAddressCoordinator.chooseDownstreamAddress(prefix));
}
private void verifyNotifyConflictAndRelease(final IpServer ipServer) throws Exception {
@@ -543,41 +277,24 @@
}
@Test
- public void testEnableLegacyWifiP2PAddress() throws Exception {
- when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
- getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress()));
- // No matter #shouldEnableWifiP2pDedicatedIp() is enabled or not, legacy wifi p2p prefix
- // is resevered.
- assertReseveredWifiP2pPrefix();
-
- when(mConfig.shouldEnableWifiP2pDedicatedIp()).thenReturn(true);
- assertReseveredWifiP2pPrefix();
-
- // If #shouldEnableWifiP2pDedicatedIp() is enabled, wifi P2P gets the configured address.
- LinkAddress address = requestDownstreamAddress(mWifiP2pIpServer,
- 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);
+ assertNotNull(hotspotAddress);
final LinkAddress localHotspotAddress = requestDownstreamAddress(mLocalHotspotIpServer,
CONNECTIVITY_SCOPE_LOCAL, true /* useLastAddress */);
- assertEquals("Wrong local hotspot prefix: ", new LinkAddress("192.168.134.5/24"),
- localHotspotAddress);
+ assertNotNull(localHotspotAddress);
+
+ final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddress);
+ final IpPrefix localHotspotPrefix = asIpPrefix(localHotspotAddress);
+ assertFalse(hotspotPrefix.containsPrefix(localHotspotPrefix));
+ assertFalse(localHotspotPrefix.containsPrefix(hotspotPrefix));
}
@Test
public void testStartedPrefixRange() throws Exception {
- when(mConfig.isRandomPrefixBaseEnabled()).thenReturn(true);
+ when(mDeps.isFeatureEnabled(TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION)).thenReturn(true);
startedPrefixBaseTest("192.168.0.0/16", 0);
@@ -602,7 +319,8 @@
private void startedPrefixBaseTest(final String expected, final int randomIntForPrefixBase)
throws Exception {
- mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig));
+ mPrivateAddressCoordinator =
+ spy(new PrivateAddressCoordinator(mConnectivityMgr::getAllNetworks, mDeps));
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomIntForPrefixBase);
final LinkAddress address = requestDownstreamAddress(mHotspotIpServer,
CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 6ba5d48..66fe957 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -71,6 +71,7 @@
import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
+import static com.android.networkstack.tethering.PrivateAddressCoordinator.TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION;
import static com.android.networkstack.tethering.TestConnectivityManager.BROADCAST_FIRST;
import static com.android.networkstack.tethering.TestConnectivityManager.CALLBACKS_FIRST;
import static com.android.networkstack.tethering.Tethering.UserRestrictionActionListener;
@@ -124,6 +125,7 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.EthernetManager;
import android.net.EthernetManager.TetheredInterfaceCallback;
@@ -292,6 +294,7 @@
@Mock private TetheredInterfaceRequestShim mTetheredInterfaceRequestShim;
@Mock private TetheringMetrics mTetheringMetrics;
@Mock private RoutingCoordinatorManager mRoutingCoordinatorManager;
+ @Mock private PrivateAddressCoordinator.Dependencies mPrivateAddressCoordinatorDependencies;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -374,6 +377,7 @@
@Override
public String getSystemServiceName(Class<?> serviceClass) {
if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE;
+ if (ConnectivityManager.class.equals(serviceClass)) return Context.CONNECTIVITY_SERVICE;
return super.getSystemServiceName(serviceClass);
}
}
@@ -533,9 +537,11 @@
}
@Override
- public PrivateAddressCoordinator makePrivateAddressCoordinator(Context ctx,
- TetheringConfiguration cfg) {
- mPrivateAddressCoordinator = super.makePrivateAddressCoordinator(ctx, cfg);
+ public PrivateAddressCoordinator makePrivateAddressCoordinator(Context ctx) {
+ ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class);
+ mPrivateAddressCoordinator =
+ new PrivateAddressCoordinator(
+ cm::getAllNetworks, mPrivateAddressCoordinatorDependencies);
return mPrivateAddressCoordinator;
}
@@ -662,6 +668,8 @@
.thenReturn(true);
initOffloadConfiguration(OFFLOAD_HAL_VERSION_HIDL_1_0, 0 /* defaultDisabled */);
when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats);
+ when(mPrivateAddressCoordinatorDependencies.isFeatureEnabled(anyString()))
+ .thenReturn(false);
mServiceContext = new TestContext(mContext);
mServiceContext.setUseRegisteredHandlers(true);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
index 34689bc..6b646ec 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
@@ -87,13 +87,13 @@
import android.util.ArrayMap;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.networkstack.tethering.UpstreamNetworkState;
import com.android.networkstack.tethering.metrics.TetheringMetrics.DataUsage;
import com.android.networkstack.tethering.metrics.TetheringMetrics.Dependencies;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
import org.junit.After;
@@ -104,7 +104,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-@RunWith(AndroidJUnit4.class)
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
public final class TetheringMetricsTest {
@Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
@@ -158,7 +159,7 @@
mThread = new HandlerThread("TetheringMetricsTest");
mThread.start();
mHandler = new Handler(mThread.getLooper());
- doReturn(mHandler).when(mDeps).createHandler(any());
+ doReturn(mHandler).when(mDeps).createHandler();
// Set up the usage for upstream types.
mMockUpstreamUsageBaseline.put(UT_CELLULAR, new DataUsage(100L, 200L));
mMockUpstreamUsageBaseline.put(UT_WIFI, new DataUsage(400L, 800L));
@@ -498,7 +499,7 @@
private void verifyEmptyUsageForAllUpstreamTypes() {
mHandler.post(() -> {
for (UpstreamType type : UpstreamType.values()) {
- assertEquals(EMPTY, mTetheringMetrics.getDataUsageFromUpstreamType(type));
+ assertEquals(EMPTY, mTetheringMetrics.getLastReportedUsageFromUpstreamType(type));
}
});
HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
@@ -555,7 +556,8 @@
mHandler.post(() -> {
for (UpstreamType type : UpstreamType.values()) {
- final DataUsage dataUsage = mTetheringMetrics.getDataUsageFromUpstreamType(type);
+ final DataUsage dataUsage =
+ mTetheringMetrics.getLastReportedUsageFromUpstreamType(type);
if (TetheringMetrics.isUsageSupportedForUpstreamType(type)) {
assertEquals(mMockUpstreamUsageBaseline.get(type), dataUsage);
} else {
@@ -610,12 +612,21 @@
incrementCurrentTime(cellDuration);
updateUpstreamDataUsage(UT_CELLULAR, cellUsageDiff);
+ // Change the upstream back to Wi-FI and update the data usage
+ runAndWaitForIdle(() ->
+ mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI)));
+ final long wifiDuration2 = 50 * SECOND_IN_MILLIS;
+ final long wifiUsageDiff2 = 1000L;
+ incrementCurrentTime(wifiDuration2);
+ updateUpstreamDataUsage(UT_WIFI, wifiUsageDiff2);
+
// Stop tethering and verify that the data usage is uploaded.
updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder();
addUpstreamEvent(upstreamEvents, UT_WIFI, wifiDuration, wifiUsageDiff, wifiUsageDiff);
addUpstreamEvent(upstreamEvents, UT_BLUETOOTH, bluetoothDuration, btUsageDiff, btUsageDiff);
addUpstreamEvent(upstreamEvents, UT_CELLULAR, cellDuration, cellUsageDiff, cellUsageDiff);
+ addUpstreamEvent(upstreamEvents, UT_WIFI, wifiDuration2, wifiUsageDiff2, wifiUsageDiff2);
verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR,
UserType.USER_SETTINGS, upstreamEvents,
currentTimeMillis() - wifiTetheringStartTime);
diff --git a/DnsResolver/Android.bp b/bpf/dns_helper/Android.bp
similarity index 100%
rename from DnsResolver/Android.bp
rename to bpf/dns_helper/Android.bp
diff --git a/DnsResolver/DnsBpfHelper.cpp b/bpf/dns_helper/DnsBpfHelper.cpp
similarity index 100%
rename from DnsResolver/DnsBpfHelper.cpp
rename to bpf/dns_helper/DnsBpfHelper.cpp
diff --git a/DnsResolver/DnsBpfHelper.h b/bpf/dns_helper/DnsBpfHelper.h
similarity index 100%
rename from DnsResolver/DnsBpfHelper.h
rename to bpf/dns_helper/DnsBpfHelper.h
diff --git a/DnsResolver/DnsBpfHelperTest.cpp b/bpf/dns_helper/DnsBpfHelperTest.cpp
similarity index 100%
rename from DnsResolver/DnsBpfHelperTest.cpp
rename to bpf/dns_helper/DnsBpfHelperTest.cpp
diff --git a/DnsResolver/DnsHelper.cpp b/bpf/dns_helper/DnsHelper.cpp
similarity index 100%
rename from DnsResolver/DnsHelper.cpp
rename to bpf/dns_helper/DnsHelper.cpp
diff --git a/DnsResolver/include/DnsHelperPublic.h b/bpf/dns_helper/include/DnsHelperPublic.h
similarity index 100%
rename from DnsResolver/include/DnsHelperPublic.h
rename to bpf/dns_helper/include/DnsHelperPublic.h
diff --git a/DnsResolver/libcom.android.tethering.dns_helper.map.txt b/bpf/dns_helper/libcom.android.tethering.dns_helper.map.txt
similarity index 100%
rename from DnsResolver/libcom.android.tethering.dns_helper.map.txt
rename to bpf/dns_helper/libcom.android.tethering.dns_helper.map.txt
diff --git a/bpf/headers/include/bpf/KernelUtils.h b/bpf/headers/include/bpf/KernelUtils.h
index 417a5c4..68bc607 100644
--- a/bpf/headers/include/bpf/KernelUtils.h
+++ b/bpf/headers/include/bpf/KernelUtils.h
@@ -55,11 +55,12 @@
isKernelVersion(4, 9) || // minimum for Android S & T
isKernelVersion(4, 14) || // minimum for Android U
isKernelVersion(4, 19) || // minimum for Android V
- isKernelVersion(5, 4) || // first supported in Android R
+ isKernelVersion(5, 4) || // first supported in Android R, min for W
isKernelVersion(5, 10) || // first supported in Android S
isKernelVersion(5, 15) || // first supported in Android T
isKernelVersion(6, 1) || // first supported in Android U
- isKernelVersion(6, 6); // first supported in Android V
+ isKernelVersion(6, 6) || // first supported in Android V
+ isKernelVersion(6, 12); // first supported in Android W
}
// Figure out the bitness of userspace.
diff --git a/bpf/loader/NetBpfLoad.cpp b/bpf/loader/NetBpfLoad.cpp
index f369458..9a049c7 100644
--- a/bpf/loader/NetBpfLoad.cpp
+++ b/bpf/loader/NetBpfLoad.cpp
@@ -59,6 +59,9 @@
#include "bpf/BpfUtils.h"
#include "bpf_map_def.h"
+// The following matches bpf_helpers.h, which is only for inclusion in bpf code
+#define BPFLOADER_MAINLINE_VERSION 42u
+
using android::base::EndsWith;
using android::base::GetIntProperty;
using android::base::GetProperty;
@@ -1154,7 +1157,10 @@
ALOGV("map_fd found at %d is %d in %s", i, mapFds[i].get(), elfPath);
ret = readCodeSections(elfFile, cs);
- if (ret == -ENOENT) return 0; // no programs defined in this .o
+ // BPF .o's with no programs are only supported by mainline netbpfload,
+ // make sure .o's targeting non-mainline (ie. S) bpfloader don't show up.
+ if (ret == -ENOENT && bpfLoaderMinVer >= BPFLOADER_MAINLINE_VERSION)
+ return 0;
if (ret) {
ALOGE("Couldn't read all code sections in %s", elfPath);
return ret;
@@ -1419,7 +1425,7 @@
const bool has_platform_netbpfload_rc = exists("/system/etc/init/netbpfload.rc");
// Version of Network BpfLoader depends on the Android OS version
- unsigned int bpfloader_ver = 42u; // [42] BPFLOADER_MAINLINE_VERSION
+ unsigned int bpfloader_ver = BPFLOADER_MAINLINE_VERSION; // [42u]
if (isAtLeastT) ++bpfloader_ver; // [43] BPFLOADER_MAINLINE_T_VERSION
if (isAtLeastU) ++bpfloader_ver; // [44] BPFLOADER_MAINLINE_U_VERSION
if (runningAsRoot) ++bpfloader_ver; // [45] BPFLOADER_MAINLINE_U_QPR3_VERSION
diff --git a/bpf/loader/initrc-doc/README.txt b/bpf/loader/initrc-doc/README.txt
index 42e1fc2..2b22326 100644
--- a/bpf/loader/initrc-doc/README.txt
+++ b/bpf/loader/initrc-doc/README.txt
@@ -1,20 +1,42 @@
This directory contains comment stripped versions of
//system/bpf/bpfloader/bpfloader.rc
-from previous versions of Android.
+or
+ //packages/modules/Connectivity/bpf/loader/netbpfload.rc
+(as appropriate) from previous versions of Android.
Generated via:
- (cd ../../../../../system/bpf && git cat-file -p remotes/aosp/android11-release:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk30-11-R.rc
- (cd ../../../../../system/bpf && git cat-file -p remotes/aosp/android12-release:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk31-12-S.rc
- (cd ../../../../../system/bpf && git cat-file -p remotes/aosp/android13-release:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk33-13-T.rc
- (cd ../../../../../system/bpf && git cat-file -p remotes/aosp/android14-release:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk34-14-U.rc
- (cd ../../../../../system/bpf && git cat-file -p remotes/aosp/main:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk34-14-U-QPR2.rc
+ (cd ../../../../../../system/bpf && git cat-file -p remotes/aosp/android11-release:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk30-11-R.rc
+ (cd ../../../../../../system/bpf && git cat-file -p remotes/aosp/android12-release:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk31-12-S.rc
+ (cd ../../../../../../system/bpf && git cat-file -p remotes/aosp/android13-release:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk33-13-T.rc
+ (cd ../../../../../../system/bpf && git cat-file -p remotes/aosp/android14-release:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk34-14-U.rc
+ git cat-file -p remotes/aosp/android14-qpr2-release:netbpfload/netbpfload.rc | egrep -v '^ *#' > bpfloader-sdk34-14-U-QPR2-24Q1.rc
+ git cat-file -p remotes/aosp/android14-qpr3-release:netbpfload/netbpfload.rc | egrep -v '^ *#' > bpfloader-sdk34-14-U-QPR3-24Q2.rc
+ git cat-file -p remotes/aosp/android15-release:netbpfload/netbpfload.rc | egrep -v '^ *#' > bpfloader-sdk35-15-V-24Q3.rc
+ git cat-file -p remotes/aosp/main:bpf/loader/netbpfload.rc | egrep -v '^ *#' > bpfloader-sdk35-15-V-QPR1-24Q4.rc
+
+see also:
+ https://android.googlesource.com/platform/system/bpf/+/refs/heads/android11-release/bpfloader/bpfloader.rc
+ https://android.googlesource.com/platform/system/bpf/+/refs/heads/android12-release/bpfloader/bpfloader.rc
+ https://android.googlesource.com/platform/system/bpf/+/refs/heads/android13-release/bpfloader/bpfloader.rc
+ https://android.googlesource.com/platform/system/bpf/+/refs/heads/android14-release/bpfloader/bpfloader.rc
+ https://android.googlesource.com/platform/system/bpf/+/refs/heads/android14-qpr1-release/bpfloader/bpfloader.rc
+ https://android.googlesource.com/platform/system/bpf/+/refs/heads/android14-qpr2-release/bpfloader/ (rc file is gone in QPR2)
+ https://android.googlesource.com/platform/packages/modules/Connectivity/+/refs/heads/android14-qpr2-release/netbpfload/netbpfload.rc
+ https://android.googlesource.com/platform/packages/modules/Connectivity/+/refs/heads/android14-qpr3-release/netbpfload/netbpfload.rc
+ https://android.googlesource.com/platform/packages/modules/Connectivity/+/refs/heads/android15-release/netbpfload/netbpfload.rc
+ https://android.googlesource.com/platform/packages/modules/Connectivity/+/refs/heads/android15-qpr1-release/netbpfload/netbpfload.rc
+ https://android.googlesource.com/platform/packages/modules/Connectivity/+/refs/heads/main/netbpfload/netbpfload.rc
+or:
+ https://googleplex-android.googlesource.com/platform/packages/modules/Connectivity/+/refs/heads/24Q1-release/netbpfload/netbpfload.rc
+ https://googleplex-android.googlesource.com/platform/packages/modules/Connectivity/+/refs/heads/24Q2-release/netbpfload/netbpfload.rc
+ https://googleplex-android.googlesource.com/platform/packages/modules/Connectivity/+/refs/heads/24Q3-release/netbpfload/netbpfload.rc
+ https://googleplex-android.googlesource.com/platform/packages/modules/Connectivity/+/refs/heads/24Q4-release/bpf/loader/netbpfload.rc
this is entirely equivalent to:
(cd /android1/system/bpf && git cat-file -p remotes/goog/rvc-dev:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk30-11-R.rc
(cd /android1/system/bpf && git cat-file -p remotes/goog/sc-dev:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk31-12-S.rc
(cd /android1/system/bpf && git cat-file -p remotes/goog/tm-dev:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk33-13-T.rc
(cd /android1/system/bpf && git cat-file -p remotes/goog/udc-dev:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk34-14-U.rc
- (cd /android1/system/bpf && git cat-file -p remotes/goog/main:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk34-14-U-QPR2.rc
it is also equivalent to:
(cd /android1/system/bpf && git cat-file -p remotes/goog/rvc-qpr-dev:bpfloader/bpfloader.rc; ) | egrep -v '^ *#' > bpfloader-sdk30-11-R.rc
@@ -29,34 +51,66 @@
Key takeaways:
-= R bpfloader:
+= R bpfloader (platform)
- CHOWN + SYS_ADMIN
- asynchronous startup
- platform only
- proc file setup handled by initrc
-= S bpfloader
+= S bpfloader (platform)
- adds NET_ADMIN
- synchronous startup
- platform + mainline tethering offload
-= T bpfloader
+= T bpfloader (platform)
- platform + mainline networking (including tethering offload)
- supported btf for maps via exec of btfloader
-= U bpfloader
+= U bpfloader (platform)
- proc file setup moved into bpfloader binary
- explicitly specified user and groups:
group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
user root
-= U QPR2 bpfloader
+= U QPR2 [24Q1] bpfloader (platform netbpfload -> platform bpfloader)
- drops support of btf for maps
- invocation of /system/bin/netbpfload binary, which after handling *all*
networking bpf related things executes the platform /system/bin/bpfloader
which handles non-networking bpf.
+ - Note: this does not (by itself) call into apex NetBpfLoad
+
+= U QPR3 [24Q2] bpfloader (platform netbpfload -> apex netbpfload -> platform bpfloader)
+ - platform NetBpfload *always* execs into apex NetBpfLoad,
+ - shipped with mainline tethering apex that includes NetBpfLoad binary.
+
+= V [24Q3] bpfloader (apex netbpfload -> platform bpfloader)
+ - no significant changes, though it does hard require the apex NetBpfLoad
+ by virtue of the platform NetBpfLoad no longer being present.
+ ie. the apex must override the platform 'bpfloader' service for 35+:
+ the V FRC M-2024-08+ tethering apex does this.
+
+= V QPR1 [24Q4] bpfloader (apex netbpfload -> platform bpfloader)
+ - made netd start earlier (previously happened in parallel to zygote)
+ - renamed and moved the trigger out of netbpload.rc into
+ //system/core/rootdir/init.rc
+ - the new sequence is:
+ trigger post-fs-data (logd available, starts apexd)
+ trigger load-bpf-programs (does: exec_start bpfloader)
+ trigger bpf-progs-loaded (does: start netd)
+ trigger zygote-start
+ - this is more or less irrelevant from the point of view of the bpfloader,
+ but it does mean netd init could fail and abort the boot earlier,
+ before 'A/B update_verifier marks a successful boot'.
+ Though note that due to netd being started asynchronously, it is racy.
Note that there is now a copy of 'netbpfload' provided by the tethering apex
mainline module at /apex/com.android.tethering/bin/netbpfload, which due
to the use of execve("/system/bin/bpfloader") relies on T+ selinux which was
added for btf map support (specifically the ability to exec the "btfloader").
+
+= mainline tethering apex M-2024-08+ overrides the platform service for V+
+ thus loading mainline (ie. networking) bpf programs from mainline 'NetBpfLoad'
+ and platform ones from platform 'bpfloader'.
+
+= mainline tethering apex M-2024-09+ changes T+ behaviour (U QPR3+ unaffected)
+ netd -> netd_updatable.so -> ctl.start=mdnsd_netbpfload -> load net bpf programs
diff --git a/bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR2.rc b/bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR2-24Q1.rc
similarity index 100%
copy from bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR2.rc
copy to bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR2-24Q1.rc
diff --git a/bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR2.rc b/bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR3-24Q2.rc
similarity index 100%
rename from bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR2.rc
rename to bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR3-24Q2.rc
diff --git a/bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR3.rc b/bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR3.rc
deleted file mode 100644
index 8f3f462..0000000
--- a/bpf/loader/initrc-doc/bpfloader-sdk34-14-U-QPR3.rc
+++ /dev/null
@@ -1,11 +0,0 @@
-on load_bpf_programs
- exec_start bpfloader
-
-service bpfloader /system/bin/netbpfload
- capabilities CHOWN SYS_ADMIN NET_ADMIN
- group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
- user root
- rlimit memlock 1073741824 1073741824
- oneshot
- reboot_on_failure reboot,bpfloader-failed
- updatable
diff --git a/bpf/loader/initrc-doc/bpfloader-sdk35-15-V.rc b/bpf/loader/initrc-doc/bpfloader-sdk35-15-V-24Q3.rc
similarity index 100%
rename from bpf/loader/initrc-doc/bpfloader-sdk35-15-V.rc
rename to bpf/loader/initrc-doc/bpfloader-sdk35-15-V-24Q3.rc
diff --git a/bpf/loader/initrc-doc/bpfloader-sdk35-15-V-QPR1-24Q4.rc b/bpf/loader/initrc-doc/bpfloader-sdk35-15-V-QPR1-24Q4.rc
new file mode 100644
index 0000000..e2639ac
--- /dev/null
+++ b/bpf/loader/initrc-doc/bpfloader-sdk35-15-V-QPR1-24Q4.rc
@@ -0,0 +1,5 @@
+service bpfloader /system/bin/false
+ user root
+ oneshot
+ reboot_on_failure reboot,netbpfload-missing
+ updatable
diff --git a/bpf/loader/netbpfload.rc b/bpf/loader/netbpfload.rc
index e1af47f..10bfbb2 100644
--- a/bpf/loader/netbpfload.rc
+++ b/bpf/loader/netbpfload.rc
@@ -1,22 +1,3 @@
-# zygote-start is what officially starts netd (see //system/core/rootdir/init.rc)
-# However, on some hardware it's started from post-fs-data as well, which is just
-# a tad earlier. There's no benefit to that though, since on 4.9+ P+ devices netd
-# will just block until bpfloader finishes and sets the bpf.progs_loaded property.
-#
-# It is important that we start bpfloader after:
-# - /sys/fs/bpf is already mounted,
-# - apex (incl. rollback) is initialized (so that in the future we can load bpf
-# programs shipped as part of apex mainline modules)
-# - logd is ready for us to log stuff
-#
-# At the same time we want to be as early as possible to reduce races and thus
-# failures (before memory is fragmented, and cpu is busy running tons of other
-# stuff) and we absolutely want to be before netd and the system boot slot is
-# considered to have booted successfully.
-#
-on load_bpf_programs
- exec_start bpfloader
-
# Note: This will actually execute /apex/com.android.tethering/bin/netbpfload
# by virtue of 'service bpfloader' being overridden by the apex shipped .rc
# Warning: most of the below settings are irrelevant unless the apex is missing.
diff --git a/bpf/netd/BpfHandler.cpp b/bpf/netd/BpfHandler.cpp
index 15ab19c..8e4c2c6 100644
--- a/bpf/netd/BpfHandler.cpp
+++ b/bpf/netd/BpfHandler.cpp
@@ -114,6 +114,11 @@
cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
}
+ if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_INET_RELEASE_PROG_PATH,
+ cg_fd, BPF_CGROUP_INET_SOCK_RELEASE));
+ }
+
if (modules::sdklevel::IsAtLeastV()) {
RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_CONNECT4_PROG_PATH,
cg_fd, BPF_CGROUP_INET4_CONNECT));
@@ -134,11 +139,6 @@
RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_SETSOCKOPT_PROG_PATH,
cg_fd, BPF_CGROUP_SETSOCKOPT));
}
-
- if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
- RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_INET_RELEASE_PROG_PATH,
- cg_fd, BPF_CGROUP_INET_SOCK_RELEASE));
- }
}
if (bpf::isAtLeastKernelVersion(4, 19, 0)) {
@@ -156,6 +156,10 @@
if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_BIND) <= 0) abort();
}
+ if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_RELEASE) <= 0) abort();
+ }
+
if (modules::sdklevel::IsAtLeastV()) {
if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET4_CONNECT) <= 0) abort();
if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_CONNECT) <= 0) abort();
@@ -168,10 +172,6 @@
if (bpf::queryProgram(cg_fd, BPF_CGROUP_GETSOCKOPT) <= 0) abort();
if (bpf::queryProgram(cg_fd, BPF_CGROUP_SETSOCKOPT) <= 0) abort();
}
-
- if (bpf::isAtLeastKernelVersion(5, 10, 0)) {
- if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET_SOCK_RELEASE) <= 0) abort();
- }
}
return netdutils::status::ok;
@@ -201,7 +201,7 @@
}
}
-Status BpfHandler::init(const char* cg2_path) {
+static inline void waitForBpf() {
// Note: netd *can* be restarted, so this might get called a second time after boot is complete
// at which point we don't need to (and shouldn't) wait for (more importantly start) loading bpf
@@ -229,6 +229,21 @@
}
ALOGI("BPF programs are loaded");
+}
+
+Status BpfHandler::init(const char* cg2_path) {
+ // This wait is effectively a no-op on U QPR3+ devices (as netd starts
+ // *after* the synchronous 'exec_start bpfloader' which calls NetBpfLoad)
+ // but checking for U QPR3 is hard.
+ //
+ // Waiting should not be required on U QPR3+ devices,
+ // ...
+ //
+ // ...unless someone changed 'exec_start bpfloader' to 'start bpfloader'
+ // in the rc file.
+ //
+ // TODO: should be: if (!modules::sdklevel::IsAtLeastW())
+ if (android_get_device_api_level() <= __ANDROID_API_V__) waitForBpf();
RETURN_IF_NOT_OK(initPrograms(cg2_path));
RETURN_IF_NOT_OK(initMaps());
diff --git a/bpf/progs/Android.bp b/bpf/progs/Android.bp
index 52eb1b3..20d194c 100644
--- a/bpf/progs/Android.bp
+++ b/bpf/progs/Android.bp
@@ -47,8 +47,8 @@
"com.android.tethering",
],
visibility: [
+ "//packages/modules/Connectivity/bpf/dns_helper",
"//packages/modules/Connectivity/bpf/netd",
- "//packages/modules/Connectivity/DnsResolver",
"//packages/modules/Connectivity/service",
"//packages/modules/Connectivity/service/native/libs/libclat",
"//packages/modules/Connectivity/Tethering",
diff --git a/bpf/progs/netd.c b/bpf/progs/netd.c
index 8804ad5..cbe856d 100644
--- a/bpf/progs/netd.c
+++ b/bpf/progs/netd.c
@@ -645,8 +645,8 @@
return (get_app_permissions() & BPF_PERMISSION_INTERNET) ? BPF_ALLOW : BPF_DISALLOW;
}
-DEFINE_NETD_V_BPF_PROG_KVER("cgroupsockrelease/inet_release", AID_ROOT, AID_ROOT,
- inet_socket_release, KVER_5_10)
+DEFINE_NETD_BPF_PROG_KVER("cgroupsockrelease/inet_release", AID_ROOT, AID_ROOT,
+ inet_socket_release, KVER_5_10)
(struct bpf_sock* sk) {
uint64_t cookie = bpf_get_sk_cookie(sk);
if (cookie) bpf_cookie_tag_map_delete_elem(&cookie);
diff --git a/bpf/progs/offload.c b/bpf/progs/offload.c
index 7e1184d..631908a 100644
--- a/bpf/progs/offload.c
+++ b/bpf/progs/offload.c
@@ -85,9 +85,8 @@
// Since the program never writes via DPA (direct packet access) auto-pull/unclone logic does
// not trigger and thus we need to manually make sure we can read packet headers via DPA.
- // Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
// It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
- try_make_writable(skb, l2_header_size + IP6_HLEN + TCP_HLEN);
+ if (bpf_skb_pull_data(skb, l2_header_size + IP6_HLEN)) return TC_ACT_PIPE;
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
@@ -110,6 +109,14 @@
// If hardware offload is running and programming flows based on conntrack entries,
// try not to interfere with it.
if (ip6->nexthdr == IPPROTO_TCP) {
+ // don't need to check return code, as it's effectively checked in the next 'if' below
+ bpf_skb_pull_data(skb, l2_header_size + IP6_HLEN + TCP_HLEN);
+
+ data = (void*)(long)skb->data;
+ data_end = (void*)(long)skb->data_end;
+ eth = is_ethernet ? data : NULL; // used iff is_ethernet
+ ip6 = is_ethernet ? (void*)(eth + 1) : data;
+
struct tcphdr* tcph = (void*)(ip6 + 1);
// Make sure we can get at the tcp header
diff --git a/bpf/tests/mts/bpf_existence_test.cpp b/bpf/tests/mts/bpf_existence_test.cpp
index eaa6373..0b5b7be 100644
--- a/bpf/tests/mts/bpf_existence_test.cpp
+++ b/bpf/tests/mts/bpf_existence_test.cpp
@@ -123,6 +123,11 @@
NETD "prog_netd_bind6_inet6_bind",
};
+// Provided by *current* mainline module for T+ devices with 5.10+ kernels
+static const set<string> MAINLINE_FOR_T_5_10_PLUS = {
+ NETD "prog_netd_cgroupsockrelease_inet_release",
+};
+
// Provided by *current* mainline module for T+ devices with 5.15+ kernels
static const set<string> MAINLINE_FOR_T_5_15_PLUS = {
SHARED "prog_dscpPolicy_schedcls_set_dscp_ether",
@@ -154,11 +159,6 @@
NETD "prog_netd_setsockopt_prog",
};
-// Provided by *current* mainline module for V+ devices with 5.10+ kernels
-static const set<string> MAINLINE_FOR_V_5_10_PLUS = {
- NETD "prog_netd_cgroupsockrelease_inet_release",
-};
-
static void addAll(set<string>& a, const set<string>& b) {
a.insert(b.begin(), b.end());
}
@@ -196,6 +196,7 @@
DO_EXPECT(IsAtLeastT(), MAINLINE_FOR_T_PLUS);
DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(4, 14, 0), MAINLINE_FOR_T_4_14_PLUS);
DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(4, 19, 0), MAINLINE_FOR_T_4_19_PLUS);
+ DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_T_5_10_PLUS);
DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 15, 0), MAINLINE_FOR_T_5_15_PLUS);
// U requires Linux Kernel 4.14+, but nothing (as yet) added or removed in U.
@@ -207,7 +208,6 @@
if (IsAtLeastV()) ASSERT_TRUE(isAtLeastKernelVersion(4, 19, 0));
DO_EXPECT(IsAtLeastV(), MAINLINE_FOR_V_PLUS);
DO_EXPECT(IsAtLeastV() && isAtLeastKernelVersion(5, 4, 0), MAINLINE_FOR_V_5_4_PLUS);
- DO_EXPECT(IsAtLeastV() && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_V_5_10_PLUS);
for (const auto& file : mustExist) {
EXPECT_EQ(0, access(file.c_str(), R_OK)) << file << " does not exist";
diff --git a/common/thread_flags.aconfig b/common/thread_flags.aconfig
index c11c6c0..60120bc 100644
--- a/common/thread_flags.aconfig
+++ b/common/thread_flags.aconfig
@@ -26,3 +26,21 @@
description: "Controls whether the Android Thread setting max power of channel feature is enabled"
bug: "346686506"
}
+
+flag {
+ name: "epskc_enabled"
+ is_exported: true
+ is_fixed_read_only: true
+ namespace: "thread_network"
+ description: "Controls whether the Android Thread Ephemeral Key feature is enabled"
+ bug: "348323500"
+}
+
+flag {
+ name: "set_nat64_configuration_enabled"
+ is_exported: true
+ is_fixed_read_only: true
+ namespace: "thread_network"
+ description: "Controls whether the setConfiguration API of NAT64 feature is enabled"
+ bug: "368456504"
+}
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 9f26bcf..08129eb 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -506,8 +506,18 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.thread.ThreadConfiguration> CREATOR;
}
+ @FlaggedApi("com.android.net.thread.flags.set_nat64_configuration_enabled") public static final class ThreadConfiguration.Builder {
+ ctor @FlaggedApi("com.android.net.thread.flags.set_nat64_configuration_enabled") public ThreadConfiguration.Builder();
+ ctor @FlaggedApi("com.android.net.thread.flags.set_nat64_configuration_enabled") public ThreadConfiguration.Builder(@NonNull android.net.thread.ThreadConfiguration);
+ method @FlaggedApi("com.android.net.thread.flags.set_nat64_configuration_enabled") @NonNull public android.net.thread.ThreadConfiguration build();
+ method @FlaggedApi("com.android.net.thread.flags.set_nat64_configuration_enabled") @NonNull public android.net.thread.ThreadConfiguration.Builder setNat64Enabled(boolean);
+ }
+
@FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class ThreadNetworkController {
+ method @FlaggedApi("com.android.net.thread.flags.epskc_enabled") @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void activateEphemeralKeyMode(@NonNull java.time.Duration, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
method public void createRandomizedDataset(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.thread.ActiveOperationalDataset,android.net.thread.ThreadNetworkException>);
+ method @FlaggedApi("com.android.net.thread.flags.epskc_enabled") @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void deactivateEphemeralKeyMode(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
+ method @FlaggedApi("com.android.net.thread.flags.epskc_enabled") @NonNull public java.time.Duration getMaxEphemeralKeyLifetime();
method public int getThreadVersion();
method public static boolean isAttached(int);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void join(@NonNull android.net.thread.ActiveOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
@@ -517,6 +527,7 @@
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.StateCallback);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void scheduleMigration(@NonNull android.net.thread.PendingOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
method @FlaggedApi("com.android.net.thread.flags.channel_max_powers_enabled") @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void setChannelMaxPowers(@NonNull @Size(min=1) android.util.SparseIntArray, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
+ method @FlaggedApi("com.android.net.thread.flags.set_nat64_configuration_enabled") @RequiresPermission(android.Manifest.permission.THREAD_NETWORK_PRIVILEGED) public void setConfiguration(@NonNull android.net.thread.ThreadConfiguration, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void setEnabled(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
method @FlaggedApi("com.android.net.thread.flags.configuration_enabled") @RequiresPermission(android.Manifest.permission.THREAD_NETWORK_PRIVILEGED) public void unregisterConfigurationCallback(@NonNull java.util.function.Consumer<android.net.thread.ThreadConfiguration>);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void unregisterOperationalDatasetCallback(@NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
@@ -526,6 +537,9 @@
field public static final int DEVICE_ROLE_LEADER = 4; // 0x4
field public static final int DEVICE_ROLE_ROUTER = 3; // 0x3
field public static final int DEVICE_ROLE_STOPPED = 0; // 0x0
+ field @FlaggedApi("com.android.net.thread.flags.epskc_enabled") public static final int EPHEMERAL_KEY_DISABLED = 0; // 0x0
+ field @FlaggedApi("com.android.net.thread.flags.epskc_enabled") public static final int EPHEMERAL_KEY_ENABLED = 1; // 0x1
+ field @FlaggedApi("com.android.net.thread.flags.epskc_enabled") public static final int EPHEMERAL_KEY_IN_USE = 2; // 0x2
field public static final int MAX_POWER_CHANNEL_DISABLED = -2147483648; // 0x80000000
field public static final int STATE_DISABLED = 0; // 0x0
field public static final int STATE_DISABLING = 2; // 0x2
@@ -540,6 +554,7 @@
public static interface ThreadNetworkController.StateCallback {
method public void onDeviceRoleChanged(int);
+ method @FlaggedApi("com.android.net.thread.flags.epskc_enabled") @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public default void onEphemeralKeyStateChanged(int, @Nullable String, @Nullable java.time.Instant);
method public default void onPartitionIdChanged(long);
method public default void onThreadEnableStateChanged(int);
}
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index 39adee3..150394b 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -123,9 +123,12 @@
private static final int POWERED_OFF_FINDING_EID_LENGTH = 20;
- private static final String POWER_OFF_FINDING_SUPPORTED_PROPERTY =
+ private static final String POWER_OFF_FINDING_SUPPORTED_PROPERTY_RO =
"ro.bluetooth.finder.supported";
+ private static final String POWER_OFF_FINDING_SUPPORTED_PROPERTY_PERSIST =
+ "persist.bluetooth.finder.supported";
+
/**
* TODO(b/286137024): Remove this when CTS R5 is rolled out.
* Whether allows Fast Pair to scan.
@@ -618,7 +621,9 @@
}
private boolean isPoweredOffFindingSupported() {
- return Boolean.parseBoolean(SystemProperties.get(POWER_OFF_FINDING_SUPPORTED_PROPERTY));
+ return Boolean.parseBoolean(SystemProperties.get(POWER_OFF_FINDING_SUPPORTED_PROPERTY_RO))
+ || Boolean.parseBoolean(SystemProperties.get(
+ POWER_OFF_FINDING_SUPPORTED_PROPERTY_PERSIST));
}
private boolean areLocationAndBluetoothEnabled() {
diff --git a/networksecurity/OWNERS b/networksecurity/OWNERS
index 1a4130a..0c838c0 100644
--- a/networksecurity/OWNERS
+++ b/networksecurity/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 1479456
+bessiej@google.com
sandrom@google.com
tweek@google.com
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
index b2ef345..fd73b29 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
@@ -15,6 +15,7 @@
*/
package com.android.server.net.ct;
+import android.annotation.NonNull;
import android.annotation.RequiresApi;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
@@ -31,10 +32,13 @@
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
import java.security.KeyFactory;
+import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
+import java.util.Optional;
/** Helper class to download certificate transparency log files. */
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@@ -42,41 +46,23 @@
private static final String TAG = "CertificateTransparencyDownloader";
- // TODO: move key to a DeviceConfig flag.
- private static final byte[] PUBLIC_KEY_BYTES =
- Base64.getDecoder()
- .decode(
- "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsu0BHGnQ++W2CTdyZyxv"
- + "HHRALOZPlnu/VMVgo2m+JZ8MNbAOH2cgXb8mvOj8flsX/qPMuKIaauO+PwROMjiq"
- + "fUpcFm80Kl7i97ZQyBDYKm3MkEYYpGN+skAR2OebX9G2DfDqFY8+jUpOOWtBNr3L"
- + "rmVcwx+FcFdMjGDlrZ5JRmoJ/SeGKiORkbbu9eY1Wd0uVhz/xI5bQb0OgII7hEj+"
- + "i/IPbJqOHgB8xQ5zWAJJ0DmG+FM6o7gk403v6W3S8qRYiR84c50KppGwe4YqSMkF"
- + "bLDleGQWLoaDSpEWtESisb4JiLaY4H+Kk0EyAhPSb+49JfUozYl+lf7iFN3qRq/S"
- + "IXXTh6z0S7Qa8EYDhKGCrpI03/+qprwy+my6fpWHi6aUIk4holUCmWvFxZDfixox"
- + "K0RlqbFDl2JXMBquwlQpm8u5wrsic1ksIv9z8x9zh4PJqNpCah0ciemI3YGRQqSe"
- + "/mRRXBiSn9YQBUPcaeqCYan+snGADFwHuXCd9xIAdFBolw9R9HTedHGUfVXPJDiF"
- + "4VusfX6BRR/qaadB+bqEArF/TzuDUr6FvOR4o8lUUxgLuZ/7HO+bHnaPFKYHHSm+"
- + "+z1lVDhhYuSZ8ax3T0C3FZpb7HMjZtpEorSV5ElKJEJwrhrBCMOD8L01EoSPrGlS"
- + "1w22i9uGHMn/uGQKo28u7AsCAwEAAQ==");
-
private final Context mContext;
private final DataStore mDataStore;
private final DownloadHelper mDownloadHelper;
private final CertificateTransparencyInstaller mInstaller;
- private final byte[] mPublicKey;
+
+ @NonNull private Optional<PublicKey> mPublicKey = Optional.empty();
@VisibleForTesting
CertificateTransparencyDownloader(
Context context,
DataStore dataStore,
DownloadHelper downloadHelper,
- CertificateTransparencyInstaller installer,
- byte[] publicKey) {
+ CertificateTransparencyInstaller installer) {
mContext = context;
mDataStore = dataStore;
mDownloadHelper = downloadHelper;
mInstaller = installer;
- mPublicKey = publicKey;
}
CertificateTransparencyDownloader(Context context, DataStore dataStore) {
@@ -84,8 +70,7 @@
context,
dataStore,
new DownloadHelper(context),
- new CertificateTransparencyInstaller(),
- PUBLIC_KEY_BYTES);
+ new CertificateTransparencyInstaller());
}
void registerReceiver() {
@@ -98,6 +83,20 @@
}
}
+ void setPublicKey(String publicKey) throws GeneralSecurityException {
+ mPublicKey =
+ Optional.of(
+ KeyFactory.getInstance("RSA")
+ .generatePublic(
+ new X509EncodedKeySpec(
+ Base64.getDecoder().decode(publicKey))));
+ }
+
+ @VisibleForTesting
+ void resetPublicKey() {
+ mPublicKey = Optional.empty();
+ }
+
void startMetadataDownload(String metadataUrl) {
long downloadId = download(metadataUrl);
if (downloadId == -1) {
@@ -202,9 +201,11 @@
}
private boolean verify(Uri file, Uri signature) throws IOException, GeneralSecurityException {
+ if (!mPublicKey.isPresent()) {
+ throw new InvalidKeyException("Missing public key for signature verification");
+ }
Signature verifier = Signature.getInstance("SHA256withRSA");
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- verifier.initVerify(keyFactory.generatePublic(new X509EncodedKeySpec(mPublicKey)));
+ verifier.initVerify(mPublicKey.get());
ContentResolver contentResolver = mContext.getContentResolver();
try (InputStream fileStream = contentResolver.openInputStream(file);
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
index a263546..914af06 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
@@ -23,6 +23,7 @@
import android.text.TextUtils;
import android.util.Log;
+import java.security.GeneralSecurityException;
import java.util.concurrent.Executors;
/** Listener class for the Certificate Transparency Phenotype flags. */
@@ -57,21 +58,35 @@
return;
}
+ String newPublicKey =
+ DeviceConfig.getString(
+ Config.NAMESPACE_NETWORK_SECURITY,
+ Config.FLAG_PUBLIC_KEY,
+ /* defaultValue= */ "");
String newVersion =
- DeviceConfig.getString(Config.NAMESPACE_NETWORK_SECURITY, Config.FLAG_VERSION, "");
+ DeviceConfig.getString(
+ Config.NAMESPACE_NETWORK_SECURITY,
+ Config.FLAG_VERSION,
+ /* defaultValue= */ "");
String newContentUrl =
DeviceConfig.getString(
- Config.NAMESPACE_NETWORK_SECURITY, Config.FLAG_CONTENT_URL, "");
+ Config.NAMESPACE_NETWORK_SECURITY,
+ Config.FLAG_CONTENT_URL,
+ /* defaultValue= */ "");
String newMetadataUrl =
DeviceConfig.getString(
- Config.NAMESPACE_NETWORK_SECURITY, Config.FLAG_METADATA_URL, "");
- if (TextUtils.isEmpty(newVersion)
+ Config.NAMESPACE_NETWORK_SECURITY,
+ Config.FLAG_METADATA_URL,
+ /* defaultValue= */ "");
+ if (TextUtils.isEmpty(newPublicKey)
+ || TextUtils.isEmpty(newVersion)
|| TextUtils.isEmpty(newContentUrl)
|| TextUtils.isEmpty(newMetadataUrl)) {
return;
}
if (Config.DEBUG) {
+ Log.d(TAG, "newPublicKey=" + newPublicKey);
Log.d(TAG, "newVersion=" + newVersion);
Log.d(TAG, "newContentUrl=" + newContentUrl);
Log.d(TAG, "newMetadataUrl=" + newMetadataUrl);
@@ -88,6 +103,13 @@
return;
}
+ try {
+ mCertificateTransparencyDownloader.setPublicKey(newPublicKey);
+ } catch (GeneralSecurityException e) {
+ Log.e(TAG, "Error setting the public Key", e);
+ return;
+ }
+
// TODO: handle the case where there is already a pending download.
mDataStore.setProperty(Config.VERSION_PENDING, newVersion);
diff --git a/networksecurity/service/src/com/android/server/net/ct/Config.java b/networksecurity/service/src/com/android/server/net/ct/Config.java
index 2a6b8e2..611a5c7 100644
--- a/networksecurity/service/src/com/android/server/net/ct/Config.java
+++ b/networksecurity/service/src/com/android/server/net/ct/Config.java
@@ -40,6 +40,7 @@
static final String FLAG_CONTENT_URL = FLAGS_PREFIX + "content_url";
static final String FLAG_METADATA_URL = FLAGS_PREFIX + "metadata_url";
static final String FLAG_VERSION = FLAGS_PREFIX + "version";
+ static final String FLAG_PUBLIC_KEY = FLAGS_PREFIX + "public_key";
// properties
static final String VERSION_PENDING = "version_pending";
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
index a056c35..1aad028 100644
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
@@ -48,9 +48,10 @@
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
+import java.security.PublicKey;
import java.security.Signature;
+import java.util.Base64;
/** Tests for the {@link CertificateTransparencyDownloader}. */
@RunWith(JUnit4.class)
@@ -60,18 +61,20 @@
@Mock private CertificateTransparencyInstaller mCertificateTransparencyInstaller;
private PrivateKey mPrivateKey;
+ private PublicKey mPublicKey;
private Context mContext;
private File mTempFile;
private DataStore mDataStore;
private CertificateTransparencyDownloader mCertificateTransparencyDownloader;
@Before
- public void setUp() throws IOException, NoSuchAlgorithmException {
+ public void setUp() throws IOException, GeneralSecurityException {
MockitoAnnotations.initMocks(this);
KeyPairGenerator instance = KeyPairGenerator.getInstance("RSA");
KeyPair keyPair = instance.generateKeyPair();
mPrivateKey = keyPair.getPrivate();
+ mPublicKey = keyPair.getPublic();
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mTempFile = File.createTempFile("datastore-test", ".properties");
@@ -80,16 +83,13 @@
mCertificateTransparencyDownloader =
new CertificateTransparencyDownloader(
- mContext,
- mDataStore,
- mDownloadHelper,
- mCertificateTransparencyInstaller,
- keyPair.getPublic().getEncoded());
+ mContext, mDataStore, mDownloadHelper, mCertificateTransparencyInstaller);
}
@After
public void tearDown() {
mTempFile.delete();
+ mCertificateTransparencyDownloader.resetPublicKey();
}
@Test
@@ -155,6 +155,8 @@
long metadataId = 123;
File metadataFile = sign(logListFile);
Uri metadataUri = Uri.fromFile(metadataFile);
+ mCertificateTransparencyDownloader.setPublicKey(
+ Base64.getEncoder().encodeToString(mPublicKey.getEncoded()));
setUpDownloadComplete(version, metadataId, metadataUri, contentId, contentUri);
when(mCertificateTransparencyInstaller.install(any(), eq(version))).thenReturn(true);
@@ -212,6 +214,28 @@
assertThat(mDataStore.getProperty(Config.METADATA_URL)).isNull();
}
+ @Test
+ public void testDownloader_handleContentCompleteMissingVerificationPublicKey()
+ throws Exception {
+ String version = "666";
+ long contentId = 666;
+ File logListFile = File.createTempFile("log_list", "json");
+ Uri contentUri = Uri.fromFile(logListFile);
+ long metadataId = 123;
+ File metadataFile = sign(logListFile);
+ Uri metadataUri = Uri.fromFile(metadataFile);
+
+ setUpDownloadComplete(version, metadataId, metadataUri, contentId, contentUri);
+
+ mCertificateTransparencyDownloader.onReceive(
+ mContext, makeDownloadCompleteIntent(contentId));
+
+ verify(mCertificateTransparencyInstaller, never()).install(any(), eq(version));
+ assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
+ assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
+ assertThat(mDataStore.getProperty(Config.METADATA_URL)).isNull();
+ }
+
private Intent makeDownloadCompleteIntent(long downloadId) {
return new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId);
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 b16d8bd..f540f10 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -33,6 +33,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.DnsUtils;
+import com.android.net.module.util.HandlerUtils;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -213,6 +214,19 @@
MdnsUtils.ensureRunningOnHandlerThread(handler);
}
}
+
+ public void runWithScissorsForDumpIfReady(@NonNull Runnable function) {
+ final Handler handler;
+ synchronized (pendingTasks) {
+ if (this.handler == null) {
+ Log.d(TAG, "The handler is not ready. Ignore the DiscoveryManager dump");
+ return;
+ } else {
+ handler = this.handler;
+ }
+ }
+ HandlerUtils.runWithScissorsForDump(handler, function, 10_000);
+ }
}
/**
@@ -469,7 +483,7 @@
* Dump DiscoveryManager state.
*/
public void dump(PrintWriter pw) {
- discoveryExecutor.checkAndRunOnHandlerThread(() -> {
+ discoveryExecutor.runWithScissorsForDumpIfReady(() -> {
pw.println("Clients:");
// Dump ServiceTypeClients
for (MdnsServiceTypeClient serviceTypeClient
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java b/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java
index cfeca5d..e52dd2f 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java
@@ -107,7 +107,7 @@
final QueryTaskConfig nextRunConfig = currentConfig.getConfigForNextRun(queryMode);
long timeToRun;
if (mLastScheduledQueryTaskArgs == null && !forceEnableBackoff) {
- timeToRun = now + nextRunConfig.delayUntilNextTaskWithoutBackoffMs;
+ timeToRun = now + nextRunConfig.delayBeforeTaskWithoutBackoffMs;
} else {
timeToRun = calculateTimeToRun(mLastScheduledQueryTaskArgs,
nextRunConfig, now, minRemainingTtl, lastSentTime, numOfQueriesBeforeBackoff,
@@ -133,7 +133,7 @@
private static long calculateTimeToRun(@Nullable ScheduledQueryTaskArgs taskArgs,
QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime,
int numOfQueriesBeforeBackoff, boolean forceEnableBackoff) {
- final long baseDelayInMs = queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
+ final long baseDelayInMs = queryTaskConfig.delayBeforeTaskWithoutBackoffMs;
if (!(forceEnableBackoff
|| queryTaskConfig.shouldUseQueryBackoff(numOfQueriesBeforeBackoff))) {
return lastSentTime + baseDelayInMs;
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 a5dd536..b0e3ba4 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -20,6 +20,7 @@
import static com.android.server.connectivity.mdns.MdnsServiceCache.ServiceExpiredCallback;
import static com.android.server.connectivity.mdns.MdnsServiceCache.findMatchedResponse;
import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.buildMdnsServiceInfoFromResponse;
import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
import android.annotation.NonNull;
@@ -41,10 +42,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.net.DatagramPacket;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
import java.net.InetSocketAddress;
-import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -309,57 +307,6 @@
serviceCache.unregisterServiceExpiredCallback(cacheKey);
}
- private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(@NonNull MdnsResponse response,
- @NonNull String[] serviceTypeLabels, long elapsedRealtimeMillis) {
- String[] hostName = null;
- int port = 0;
- if (response.hasServiceRecord()) {
- hostName = response.getServiceRecord().getServiceHost();
- port = response.getServiceRecord().getServicePort();
- }
-
- final List<String> ipv4Addresses = new ArrayList<>();
- final List<String> ipv6Addresses = new ArrayList<>();
- if (response.hasInet4AddressRecord()) {
- for (MdnsInetAddressRecord inetAddressRecord : response.getInet4AddressRecords()) {
- final Inet4Address inet4Address = inetAddressRecord.getInet4Address();
- ipv4Addresses.add((inet4Address == null) ? null : inet4Address.getHostAddress());
- }
- }
- if (response.hasInet6AddressRecord()) {
- for (MdnsInetAddressRecord inetAddressRecord : response.getInet6AddressRecords()) {
- final Inet6Address inet6Address = inetAddressRecord.getInet6Address();
- ipv6Addresses.add((inet6Address == null) ? null : inet6Address.getHostAddress());
- }
- }
- String serviceInstanceName = response.getServiceInstanceName();
- if (serviceInstanceName == null) {
- throw new IllegalStateException(
- "mDNS response must have non-null service instance name");
- }
- List<String> textStrings = null;
- List<MdnsServiceInfo.TextEntry> textEntries = null;
- if (response.hasTextRecord()) {
- textStrings = response.getTextRecord().getStrings();
- textEntries = response.getTextRecord().getEntries();
- }
- Instant now = Instant.now();
- // TODO: Throw an error message if response doesn't have Inet6 or Inet4 address.
- return new MdnsServiceInfo(
- serviceInstanceName,
- serviceTypeLabels,
- response.getSubtypes(),
- hostName,
- port,
- ipv4Addresses,
- ipv6Addresses,
- textStrings,
- textEntries,
- response.getInterfaceIndex(),
- response.getNetwork(),
- now.plusMillis(response.getMinRemainingTtl(elapsedRealtimeMillis)));
- }
-
private List<MdnsResponse> getExistingServices() {
return featureFlags.isQueryWithKnownAnswerEnabled()
? serviceCache.getCachedServices(cacheKey) : Collections.emptyList();
diff --git a/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java b/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
index d2cd463..4e74159 100644
--- a/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
+++ b/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
@@ -55,22 +55,22 @@
private final int queriesPerBurst;
private final int timeBetweenBurstsInMs;
private final int burstCounter;
- final long delayUntilNextTaskWithoutBackoffMs;
+ final long delayBeforeTaskWithoutBackoffMs;
private final boolean isFirstBurst;
- private final long queryCount;
+ private final long queryIndex;
- QueryTaskConfig(long queryCount, int transactionId,
+ QueryTaskConfig(long queryIndex, int transactionId,
boolean expectUnicastResponse, boolean isFirstBurst, int burstCounter,
int queriesPerBurst, int timeBetweenBurstsInMs,
- long delayUntilNextTaskWithoutBackoffMs) {
+ long delayBeforeTaskWithoutBackoffMs) {
this.transactionId = transactionId;
this.expectUnicastResponse = expectUnicastResponse;
this.queriesPerBurst = queriesPerBurst;
this.timeBetweenBurstsInMs = timeBetweenBurstsInMs;
this.burstCounter = burstCounter;
- this.delayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
+ this.delayBeforeTaskWithoutBackoffMs = delayBeforeTaskWithoutBackoffMs;
this.isFirstBurst = isFirstBurst;
- this.queryCount = queryCount;
+ this.queryIndex = queryIndex;
}
QueryTaskConfig(int queryMode) {
@@ -82,26 +82,26 @@
// Config the scan frequency based on the scan mode.
if (queryMode == AGGRESSIVE_QUERY_MODE) {
this.timeBetweenBurstsInMs = INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS;
- this.delayUntilNextTaskWithoutBackoffMs =
+ this.delayBeforeTaskWithoutBackoffMs =
TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS;
} else if (queryMode == PASSIVE_QUERY_MODE) {
// In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and then
// in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
// queries.
this.timeBetweenBurstsInMs = MAX_TIME_BETWEEN_ACTIVE_PASSIVE_BURSTS_MS;
- this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
+ this.delayBeforeTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
} else {
// In active scan mode, sends a burst of QUERIES_PER_BURST queries,
// TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
// then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
// doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
this.timeBetweenBurstsInMs = INITIAL_TIME_BETWEEN_BURSTS_MS;
- this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
+ this.delayBeforeTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
}
- this.queryCount = 0;
+ this.queryIndex = 0;
}
- long getDelayUntilNextTaskWithoutBackoff(boolean isFirstQueryInBurst,
+ long getDelayBeforeNextTaskWithoutBackoff(boolean isFirstQueryInBurst,
boolean isLastQueryInBurst, int queryMode) {
if (isFirstQueryInBurst && queryMode == AGGRESSIVE_QUERY_MODE) {
return 0;
@@ -137,7 +137,7 @@
* Get new QueryTaskConfig for next run.
*/
public QueryTaskConfig getConfigForNextRun(int queryMode) {
- long newQueryCount = queryCount + 1;
+ long newQueryCount = queryIndex + 1;
int newTransactionId = transactionId + 1;
if (newTransactionId > UNSIGNED_SHORT_MAX_VALUE) {
newTransactionId = 1;
@@ -162,7 +162,7 @@
getNextExpectUnicastResponse(isLastQueryInBurst, queryMode), newIsFirstBurst,
newBurstCounter, newQueriesPerBurst,
getNextTimeBetweenBurstsMs(isLastQueryInBurst, queryMode),
- getDelayUntilNextTaskWithoutBackoff(
+ getDelayBeforeNextTaskWithoutBackoff(
isFirstQueryInBurst, isLastQueryInBurst, queryMode));
}
@@ -174,6 +174,6 @@
if (burstCounter != 0 || isFirstBurst) {
return false;
}
- return queryCount > numOfQueriesBeforeBackoff;
+ return queryIndex > numOfQueriesBeforeBackoff;
}
}
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 8745941..85a8961 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
@@ -30,12 +30,17 @@
import android.util.Pair;
import com.android.server.connectivity.mdns.MdnsConstants;
+import com.android.server.connectivity.mdns.MdnsInetAddressRecord;
import com.android.server.connectivity.mdns.MdnsPacket;
import com.android.server.connectivity.mdns.MdnsPacketWriter;
import com.android.server.connectivity.mdns.MdnsRecord;
+import com.android.server.connectivity.mdns.MdnsResponse;
+import com.android.server.connectivity.mdns.MdnsServiceInfo;
import java.io.IOException;
import java.net.DatagramPacket;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
@@ -43,6 +48,7 @@
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -318,4 +324,62 @@
}
return true;
}
+
+ /**
+ * Build MdnsServiceInfo object from given MdnsResponse, service type labels and current time.
+ *
+ * @param response target service response
+ * @param serviceTypeLabels service type labels
+ * @param elapsedRealtimeMillis current time.
+ */
+ public static MdnsServiceInfo buildMdnsServiceInfoFromResponse(@NonNull MdnsResponse response,
+ @NonNull String[] serviceTypeLabels, long elapsedRealtimeMillis) {
+ String[] hostName = null;
+ int port = 0;
+ if (response.hasServiceRecord()) {
+ hostName = response.getServiceRecord().getServiceHost();
+ port = response.getServiceRecord().getServicePort();
+ }
+
+ final List<String> ipv4Addresses = new ArrayList<>();
+ final List<String> ipv6Addresses = new ArrayList<>();
+ if (response.hasInet4AddressRecord()) {
+ for (MdnsInetAddressRecord inetAddressRecord : response.getInet4AddressRecords()) {
+ final Inet4Address inet4Address = inetAddressRecord.getInet4Address();
+ ipv4Addresses.add((inet4Address == null) ? null : inet4Address.getHostAddress());
+ }
+ }
+ if (response.hasInet6AddressRecord()) {
+ for (MdnsInetAddressRecord inetAddressRecord : response.getInet6AddressRecords()) {
+ final Inet6Address inet6Address = inetAddressRecord.getInet6Address();
+ ipv6Addresses.add((inet6Address == null) ? null : inet6Address.getHostAddress());
+ }
+ }
+ String serviceInstanceName = response.getServiceInstanceName();
+ if (serviceInstanceName == null) {
+ throw new IllegalStateException(
+ "mDNS response must have non-null service instance name");
+ }
+ List<String> textStrings = null;
+ List<MdnsServiceInfo.TextEntry> textEntries = null;
+ if (response.hasTextRecord()) {
+ textStrings = response.getTextRecord().getStrings();
+ textEntries = response.getTextRecord().getEntries();
+ }
+ Instant now = Instant.now();
+ // TODO: Throw an error message if response doesn't have Inet6 or Inet4 address.
+ return new MdnsServiceInfo(
+ serviceInstanceName,
+ serviceTypeLabels,
+ response.getSubtypes(),
+ hostName,
+ port,
+ ipv4Addresses,
+ ipv6Addresses,
+ textStrings,
+ textEntries,
+ response.getInterfaceIndex(),
+ response.getNetwork(),
+ now.plusMillis(response.getMinRemainingTtl(elapsedRealtimeMillis)));
+ }
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index cadc04d..1ac99e4 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -202,20 +202,6 @@
return;
}
- private static NetworkCapabilities mixInCapabilities(NetworkCapabilities nc,
- NetworkCapabilities addedNc) {
- final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(nc);
- for (int transport : addedNc.getTransportTypes()) builder.addTransportType(transport);
- for (int capability : addedNc.getCapabilities()) builder.addCapability(capability);
- return builder.build();
- }
-
- private static NetworkCapabilities createDefaultNetworkCapabilities() {
- return NetworkCapabilities.Builder
- .withoutDefaultCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET).build();
- }
-
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
protected boolean removeInterface(String interfaceName) {
NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
@@ -556,14 +542,6 @@
maybeRestart();
}
- private void ensureRunningOnEthernetHandlerThread() {
- if (mHandler.getLooper().getThread() != Thread.currentThread()) {
- throw new IllegalStateException(
- "Not running on the Ethernet thread: "
- + Thread.currentThread().getName());
- }
- }
-
private void handleOnLinkPropertiesChange(LinkProperties linkProperties) {
mLinkProperties = linkProperties;
if (mNetworkAgent != null) {
diff --git a/service/ServiceConnectivityResources/Android.bp b/service/ServiceConnectivityResources/Android.bp
index 2621256..be9b2b5 100644
--- a/service/ServiceConnectivityResources/Android.bp
+++ b/service/ServiceConnectivityResources/Android.bp
@@ -33,6 +33,7 @@
"com.android.tethering",
],
certificate: ":com.android.connectivity.resources.certificate",
+ updatable: true,
}
android_app_certificate {
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 85258f8..9d27608 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -435,6 +435,7 @@
sdk_version: "core_platform",
srcs: [
"device/com/android/net/module/util/FdEventsReader.java",
+ "device/com/android/net/module/util/HandlerUtils.java",
"device/com/android/net/module/util/SharedLog.java",
"framework/com/android/net/module/util/ByteUtils.java",
"framework/com/android/net/module/util/CollectionUtils.java",
diff --git a/staticlibs/device/com/android/net/module/util/SyncStateMachine.java b/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
index da184d3..fc0161b 100644
--- a/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
+++ b/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
@@ -225,7 +225,8 @@
consideredState = mStateInfo.get(consideredState.parent);
}
if (null == consideredState) {
- Log.wtf(mName, "Message " + msg.what + " was not handled");
+ final String state = mCurrentState == null ? "null" : mCurrentState.getName();
+ Log.wtf(mName, "Message " + msg.what + " was not handled. Current state: " + state);
}
performTransitions();
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
index b5a941b..419b338 100644
--- a/staticlibs/tests/unit/host/python/apf_utils_test.py
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -25,8 +25,14 @@
get_apf_capabilities,
get_apf_counter,
get_apf_counters_from_dumpsys,
+ get_ipv4_addresses,
+ get_ipv6_addresses,
get_hardware_address,
is_send_raw_packet_downstream_supported,
+ is_packet_capture_supported,
+ start_capture_packets,
+ stop_capture_packets,
+ get_matched_packet_counts,
send_raw_packet_downstream,
)
from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError
@@ -112,6 +118,55 @@
get_hardware_address(self.mock_ad, "wlan0")
@patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_ipv4_addresses_success(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = """
+54: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
+ inet 192.168.195.162/24 brd 192.168.195.255 scope global wlan0
+ valid_lft forever preferred_lft forever
+ inet 192.168.200.1/24 brd 192.168.200.255 scope global wlan0
+ valid_lft forever preferred_lft forever
+"""
+ ip_addresses = get_ipv4_addresses(self.mock_ad, "wlan0")
+ asserts.assert_equal(ip_addresses, ["192.168.195.162", "192.168.200.1"])
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_ipv4_addresses_not_found(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = ""
+ ip_addresses = get_ipv4_addresses(self.mock_ad, "wlan0")
+ asserts.assert_equal(ip_addresses, [])
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_ipv6_addresses_success(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = """
+54: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
+ inet6 fe80::10a3:5dff:fe52:de32/64 scope link
+ valid_lft forever preferred_lft forever
+ inet6 2001:b400:e53f:164e:9c1e:780e:d1:4658/64 scope global dynamic mngtmpaddr noprefixroute
+ valid_lft 6995sec preferred_lft 6995sec
+ inet6 fe80::3aff:2199:2d8e:20d1/64 scope link noprefixroute
+ valid_lft forever preferred_lft forever
+"""
+ ip_addresses = get_ipv6_addresses(self.mock_ad, "wlan0")
+ asserts.assert_equal(ip_addresses,
+ ["fe80::10a3:5dff:fe52:de32",
+ "2001:b400:e53f:164e:9c1e:780e:d1:4658",
+ "fe80::3aff:2199:2d8e:20d1"])
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_ipv6_address_not_found(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = ""
+ ip_addresses = get_ipv6_addresses(self.mock_ad, "wlan0")
+ asserts.assert_equal(ip_addresses, [])
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
def test_send_raw_packet_downstream_success(
self, mock_adb_shell: MagicMock
) -> None:
@@ -157,6 +212,144 @@
"Send raw packet should not be supported.",
)
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_start_capture_success(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = "success" # Successful command output
+ start_capture_packets(
+ self.mock_ad, TEST_IFACE_NAME
+ )
+ mock_adb_shell.assert_called_once_with(
+ self.mock_ad,
+ "cmd network_stack capture start"
+ f" {TEST_IFACE_NAME}"
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_start_capture_failure(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = ( # Unexpected command output
+ "Any Unexpected Output"
+ )
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ start_capture_packets(
+ self.mock_ad, TEST_IFACE_NAME
+ )
+ asserts.assert_true(
+ is_packet_capture_supported(self.mock_ad),
+ "Start capturing packets should be supported.",
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_start_capture_unsupported(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.side_effect = AdbError(
+ cmd="", stdout="Unknown command", stderr="", ret_code=3
+ )
+ with asserts.assert_raises(UnsupportedOperationException):
+ start_capture_packets(
+ self.mock_ad, TEST_IFACE_NAME
+ )
+ asserts.assert_false(
+ is_packet_capture_supported(self.mock_ad),
+ "Start capturing packets should not be supported.",
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_stop_capture_success(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = "success" # Successful command output
+ stop_capture_packets(
+ self.mock_ad, TEST_IFACE_NAME
+ )
+ mock_adb_shell.assert_called_once_with(
+ self.mock_ad,
+ "cmd network_stack capture stop"
+ f" {TEST_IFACE_NAME}"
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_stop_capture_failure(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = ( # Unexpected command output
+ "Any Unexpected Output"
+ )
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ stop_capture_packets(
+ self.mock_ad, TEST_IFACE_NAME
+ )
+ asserts.assert_true(
+ is_packet_capture_supported(self.mock_ad),
+ "Stop capturing packets should be supported.",
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_stop_capture_unsupported(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.side_effect = AdbError(
+ cmd="", stdout="Unknown command", stderr="", ret_code=3
+ )
+ with asserts.assert_raises(UnsupportedOperationException):
+ stop_capture_packets(
+ self.mock_ad, TEST_IFACE_NAME
+ )
+ asserts.assert_false(
+ is_packet_capture_supported(self.mock_ad),
+ "Stop capturing packets should not be supported.",
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_matched_packet_counts_success(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = "10" # Successful command output
+ get_matched_packet_counts(
+ self.mock_ad, TEST_IFACE_NAME, TEST_PACKET_IN_HEX
+ )
+ mock_adb_shell.assert_called_once_with(
+ self.mock_ad,
+ "cmd network_stack capture matched-packet-counts"
+ f" {TEST_IFACE_NAME} {TEST_PACKET_IN_HEX}"
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_matched_packet_counts_failure(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = ( # Unexpected command output
+ "Any Unexpected Output"
+ )
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ get_matched_packet_counts(
+ self.mock_ad, TEST_IFACE_NAME, TEST_PACKET_IN_HEX
+ )
+ asserts.assert_true(
+ is_packet_capture_supported(self.mock_ad),
+ "Get matched packet counts should be supported.",
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_matched_packet_counts_unsupported(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.side_effect = AdbError(
+ cmd="", stdout="Unknown command", stderr="", ret_code=3
+ )
+ with asserts.assert_raises(UnsupportedOperationException):
+ get_matched_packet_counts(
+ self.mock_ad, TEST_IFACE_NAME, TEST_PACKET_IN_HEX
+ )
+ asserts.assert_false(
+ is_packet_capture_supported(self.mock_ad),
+ "Get matched packet counts should not be supported.",
+ )
+
@parameterized.parameters(
("2,2048,1", ApfCapabilities(2, 2048, 1)), # Valid input
("3,1024,0", ApfCapabilities(3, 1024, 0)), # Valid input
diff --git a/staticlibs/tests/unit/host/python/packet_utils_test.py b/staticlibs/tests/unit/host/python/packet_utils_test.py
new file mode 100644
index 0000000..8ad9576
--- /dev/null
+++ b/staticlibs/tests/unit/host/python/packet_utils_test.py
@@ -0,0 +1,72 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from mobly import asserts
+from mobly import base_test
+from net_tests_utils.host.python import packet_utils
+
+class TestPacketUtils(base_test.BaseTestClass):
+ def test_unicast_arp_request(self):
+ # Using scapy to generate unicast arp request packet:
+ # eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06")
+ # arp = ARP(op=1, pdst="192.168.1.1", hwsrc="00:01:02:03:04:05", psrc="192.168.1.2")
+ # pkt = eth/arp
+ expect_arp_request = """
+ 01020304050600010203040508060001080006040001000102030405c0a80102000000000000c0a80101
+ """.upper().replace(" ", "").replace("\n", "")
+ arp_request = packet_utils.construct_arp_packet(
+ src_mac="00:01:02:03:04:05",
+ dst_mac="01:02:03:04:05:06",
+ src_ip="192.168.1.2",
+ dst_ip="192.168.1.1",
+ op=packet_utils.ARP_REQUEST_OP
+ )
+ asserts.assert_equal(expect_arp_request, arp_request)
+
+ def test_broadcast_arp_request(self):
+ # Using scapy to generate unicast arp request packet:
+ # eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF")
+ # arp = ARP(op=1, pdst="192.168.1.1", hwsrc="00:01:02:03:04:05", psrc="192.168.1.2")
+ # pkt = eth/arp
+ expect_arp_request = """
+ ffffffffffff00010203040508060001080006040001000102030405c0a80102000000000000c0a80101
+ """.upper().replace(" ", "").replace("\n", "")
+ arp_request = packet_utils.construct_arp_packet(
+ src_mac="00:01:02:03:04:05",
+ dst_mac=packet_utils.ETHER_BROADCAST_MAC_ADDRESS,
+ src_ip="192.168.1.2",
+ dst_ip="192.168.1.1",
+ op=packet_utils.ARP_REQUEST_OP
+ )
+ asserts.assert_equal(expect_arp_request, arp_request)
+
+ def test_arp_reply(self):
+ # Using scapy to generate unicast arp request packet:
+ # eth = Ether(src="01:02:03:04:05:06", dst="00:01:02:03:04:05")
+ # arp = ARP(op=2, pdst="192.168.1.2", \
+ # hwsrc="01:02:03:04:05:06", \
+ # psrc="192.168.1.1", \
+ # hwdst="00:01:02:03:04:05")
+ # pkt = eth/arp
+ expect_arp_reply = """
+ 00010203040501020304050608060001080006040002010203040506c0a80101000102030405c0a80102
+ """.upper().replace(" ", "").replace("\n", "")
+ arp_reply = packet_utils.construct_arp_packet(
+ src_mac="01:02:03:04:05:06",
+ dst_mac="00:01:02:03:04:05",
+ src_ip="192.168.1.1",
+ dst_ip="192.168.1.2",
+ op=packet_utils.ARP_REPLY_OP
+ )
+ asserts.assert_equal(expect_arp_reply, arp_reply)
diff --git a/staticlibs/tests/unit/host/python/run_tests.py b/staticlibs/tests/unit/host/python/run_tests.py
index fa6a310..498dbaf 100644
--- a/staticlibs/tests/unit/host/python/run_tests.py
+++ b/staticlibs/tests/unit/host/python/run_tests.py
@@ -18,6 +18,7 @@
from host.python.adb_utils_test import TestAdbUtils
from host.python.apf_utils_test import TestApfUtils
from host.python.assert_utils_test import TestAssertUtils
+from host.python.packet_utils_test import TestPacketUtils
from mobly import suite_runner
@@ -31,5 +32,5 @@
sys.argv.pop(1)
# TODO: make the tests can be executed without manually list classes.
suite_runner.run_suite(
- [TestAssertUtils, TestAdbUtils, TestApfUtils], sys.argv
+ [TestAssertUtils, TestAdbUtils, TestApfUtils, TestPacketUtils], sys.argv
)
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 8c71a91..13e1dc0 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -33,6 +33,7 @@
],
static_libs: [
"androidx.test.ext.junit",
+ "collector-device-lib",
"kotlin-reflect",
"libnanohttpd",
"net-tests-utils-host-device-common",
diff --git a/staticlibs/testutils/devicetests/NSResponder.kt b/staticlibs/testutils/devicetests/NSResponder.kt
index f7619cd..f094407 100644
--- a/staticlibs/testutils/devicetests/NSResponder.kt
+++ b/staticlibs/testutils/devicetests/NSResponder.kt
@@ -35,12 +35,12 @@
private const val NS_TYPE = 135.toShort()
/**
- * A class that can be used to reply to Neighbor Solicitation packets on a [TapPacketReader].
+ * A class that can be used to reply to Neighbor Solicitation packets on a [PollPacketReader].
*/
class NSResponder(
- reader: TapPacketReader,
- table: Map<Inet6Address, MacAddress>,
- name: String = NSResponder::class.java.simpleName
+ reader: PollPacketReader,
+ table: Map<Inet6Address, MacAddress>,
+ name: String = NSResponder::class.java.simpleName
) : PacketResponder(reader, Icmpv6Filter(), name) {
companion object {
private val TAG = NSResponder::class.simpleName
@@ -49,7 +49,7 @@
// Copy the map if not already immutable (toMap) to make sure it is not modified
private val table = table.toMap()
- override fun replyToPacket(packet: ByteArray, reader: TapPacketReader) {
+ override fun replyToPacket(packet: ByteArray, reader: PollPacketReader) {
if (packet.size < IPV6_HEADER_LENGTH) {
return
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ArpResponder.kt b/staticlibs/testutils/devicetests/com/android/testutils/ArpResponder.kt
index cf0490c..f4c8657 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ArpResponder.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ArpResponder.kt
@@ -30,17 +30,17 @@
private val ARP_REPLY_IPV4 = byteArrayOf(0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x02)
/**
- * A class that can be used to reply to ARP packets on a [TapPacketReader].
+ * A class that can be used to reply to ARP packets on a [PollPacketReader].
*/
class ArpResponder(
- reader: TapPacketReader,
- table: Map<Inet4Address, MacAddress>,
- name: String = ArpResponder::class.java.simpleName
+ reader: PollPacketReader,
+ table: Map<Inet4Address, MacAddress>,
+ name: String = ArpResponder::class.java.simpleName
) : PacketResponder(reader, ArpRequestFilter(), name) {
// Copy the map if not already immutable (toMap) to make sure it is not modified
private val table = table.toMap()
- override fun replyToPacket(packet: ByteArray, reader: TapPacketReader) {
+ override fun replyToPacket(packet: ByteArray, reader: PollPacketReader) {
val targetIp = InetAddress.getByAddress(
packet.copyFromIndexWithLength(ARP_TARGET_IPADDR_OFFSET, 4))
as Inet4Address
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
new file mode 100644
index 0000000..f5a5b4d
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils
+
+import android.device.collectors.BaseMetricListener
+import android.device.collectors.DataRecord
+import android.os.Build
+import android.os.ParcelFileDescriptor
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.PrintWriter
+import java.time.ZonedDateTime
+import kotlin.test.assertNull
+import org.junit.AssumptionViolatedException
+import org.junit.runner.Description
+import org.junit.runner.notification.Failure
+
+/**
+ * A diagnostics collector that outputs diagnostics files as test artifacts.
+ *
+ * <p>Collects diagnostics automatically by default on non-local builds. Can be enabled/disabled
+ * manually with:
+ * ```
+ * atest MyModule -- \
+ * --module-arg MyModule:instrumentation-arg:connectivity-diagnostics-on-failure:=false
+ * ```
+ */
+class ConnectivityDiagnosticsCollector : BaseMetricListener() {
+ companion object {
+ private const val ARG_RUN_ON_FAILURE = "connectivity-diagnostics-on-failure"
+ private const val COLLECTOR_DIR = "run_listeners/connectivity_diagnostics"
+ private const val FILENAME_SUFFIX = "_conndiag.txt"
+ private const val MAX_DUMPS = 20
+
+ private val TAG = ConnectivityDiagnosticsCollector::class.simpleName
+ var instance: ConnectivityDiagnosticsCollector? = null
+ }
+
+ private val buffer = ByteArrayOutputStream()
+ private val collectorDir: File by lazy {
+ createAndEmptyDirectory(COLLECTOR_DIR)
+ }
+ private val outputFiles = mutableSetOf<String>()
+
+ override fun onSetUp() {
+ assertNull(instance, "ConnectivityDiagnosticsCollectors were set up multiple times")
+ instance = this
+ TryTestConfig.setDiagnosticsCollector { throwable ->
+ if (runOnFailure(throwable)) {
+ collectTestFailureDiagnostics(throwable)
+ }
+ }
+ }
+
+ override fun onCleanUp() {
+ instance = null
+ }
+
+ override fun onTestFail(testData: DataRecord, description: Description, failure: Failure) {
+ // TODO: find a way to disable this behavior only on local runs, to avoid slowing them down
+ // when iterating on failing tests.
+ if (!runOnFailure(failure.exception)) return
+ if (outputFiles.size >= MAX_DUMPS) return
+ Log.i(TAG, "Collecting diagnostics for test failure. Disable by running tests with: " +
+ "atest MyModule -- " +
+ "--module-arg MyModule:instrumentation-arg:$ARG_RUN_ON_FAILURE:=false")
+ collectTestFailureDiagnostics(failure.exception)
+
+ val baseFilename = "${description.className}#${description.methodName}_failure"
+ flushBufferToFileMetric(testData, baseFilename)
+ }
+
+ override fun onTestEnd(testData: DataRecord, description: Description) {
+ // Tests may call methods like collectDumpsysConnectivity to collect diagnostics at any time
+ // during the run, for example to observe state at various points to investigate a flake
+ // and compare passing/failing cases.
+ // Flush the contents of the buffer to a file when the test ends, even when successful.
+ if (buffer.size() == 0) return
+ if (outputFiles.size >= MAX_DUMPS) return
+
+ // Flush any data that the test added to the buffer for dumping
+ val baseFilename = "${description.className}#${description.methodName}_testdump"
+ flushBufferToFileMetric(testData, baseFilename)
+ }
+
+ private fun runOnFailure(exception: Throwable): Boolean {
+ // Assumption failures (assumeTrue/assumeFalse) are not actual failures
+ if (exception is AssumptionViolatedException) return false
+
+ // Do not run on local builds (which have ro.build.version.incremental set to eng.username)
+ // to avoid slowing down local runs.
+ val enabledByDefault = !Build.VERSION.INCREMENTAL.startsWith("eng.")
+ return argsBundle.getString(ARG_RUN_ON_FAILURE)?.toBooleanStrictOrNull() ?: enabledByDefault
+ }
+
+ private fun flushBufferToFileMetric(testData: DataRecord, baseFilename: String) {
+ var filename = baseFilename
+ // In case a method was run multiple times (typically retries), append a number
+ var i = 2
+ while (outputFiles.contains(filename)) {
+ filename = baseFilename + "_$i"
+ i++
+ }
+ val outFile = File(collectorDir, filename + FILENAME_SUFFIX)
+ outputFiles.add(filename)
+ outFile.writeBytes(buffer.toByteArray())
+ buffer.reset()
+ val fileKey = "${ConnectivityDiagnosticsCollector::class.qualifiedName}_$filename"
+ testData.addFileMetric(fileKey, outFile)
+ }
+
+ /**
+ * Add connectivity diagnostics to the test data dump.
+ *
+ * <p>This collects a set of diagnostics that are relevant to connectivity test failures.
+ * <p>The dump will be collected immediately, and exported to a test artifact file when the
+ * test ends.
+ * @param exceptionContext An exception to write a stacktrace to the dump for context.
+ */
+ fun collectTestFailureDiagnostics(exceptionContext: Throwable? = null) {
+ collectDumpsysConnectivity(exceptionContext)
+ }
+
+ /**
+ * Add dumpsys connectivity to the test data dump.
+ *
+ * <p>The dump will be collected immediately, and exported to a test artifact file when the
+ * test ends.
+ * @param exceptionContext An exception to write a stacktrace to the dump for context.
+ */
+ fun collectDumpsysConnectivity(exceptionContext: Throwable? = null) {
+ Log.i(TAG, "Collecting dumpsys connectivity for test artifacts")
+ PrintWriter(buffer).let {
+ it.println("--- Dumpsys connectivity at ${ZonedDateTime.now()} ---")
+ maybeWriteExceptionContext(it, exceptionContext)
+ it.flush()
+ }
+ ParcelFileDescriptor.AutoCloseInputStream(
+ InstrumentationRegistry.getInstrumentation().uiAutomation.executeShellCommand(
+ "dumpsys connectivity --dump-priority HIGH")).use {
+ it.copyTo(buffer)
+ }
+ }
+
+ private fun maybeWriteExceptionContext(writer: PrintWriter, exceptionContext: Throwable?) {
+ if (exceptionContext == null) return
+ writer.println("At: ")
+ exceptionContext.printStackTrace(writer)
+ }
+}
\ No newline at end of file
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/MdnsTestUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/MdnsTestUtils.kt
index 8b88224..5729452 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/MdnsTestUtils.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/MdnsTestUtils.kt
@@ -28,8 +28,6 @@
import com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN
import com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN
import com.android.net.module.util.TrackRecord
-import com.android.testutils.IPv6UdpFilter
-import com.android.testutils.TapPacketReader
import java.net.Inet6Address
import java.net.InetAddress
import kotlin.test.assertEquals
@@ -246,7 +244,7 @@
as Inet6Address
}
-fun TapPacketReader.pollForMdnsPacket(
+fun PollPacketReader.pollForMdnsPacket(
timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS,
predicate: (TestDnsPacket) -> Boolean
): TestDnsPacket? {
@@ -264,7 +262,7 @@
}
}
-fun TapPacketReader.pollForProbe(
+fun PollPacketReader.pollForProbe(
serviceName: String,
serviceType: String,
timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS
@@ -272,7 +270,7 @@
it.isProbeFor("$serviceName.$serviceType.local")
}
-fun TapPacketReader.pollForAdvertisement(
+fun PollPacketReader.pollForAdvertisement(
serviceName: String,
serviceType: String,
timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS
@@ -280,19 +278,19 @@
it.isReplyFor("$serviceName.$serviceType.local")
}
-fun TapPacketReader.pollForQuery(
+fun PollPacketReader.pollForQuery(
recordName: String,
vararg requiredTypes: Int,
timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS
): TestDnsPacket? = pollForMdnsPacket(timeoutMs) { it.isQueryFor(recordName, *requiredTypes) }
-fun TapPacketReader.pollForReply(
+fun PollPacketReader.pollForReply(
recordName: String,
type: Int,
timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS
): TestDnsPacket? = pollForMdnsPacket(timeoutMs) { it.isReplyFor(recordName, type) }
-fun TapPacketReader.pollForReply(
+fun PollPacketReader.pollForReply(
serviceName: String,
serviceType: String,
timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt
index 964c6c6..62d0e82 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt
@@ -21,24 +21,24 @@
private const val POLL_FREQUENCY_MS = 1000L
/**
- * A class that can be used to reply to packets from a [TapPacketReader].
+ * A class that can be used to reply to packets from a [PollPacketReader].
*
* A reply thread will be created to reply to incoming packets asynchronously.
- * The receiver creates a new read head on the [TapPacketReader], to read packets, so it does not
- * affect packets obtained through [TapPacketReader.popPacket].
+ * The receiver creates a new read head on the [PollPacketReader], to read packets, so it does not
+ * affect packets obtained through [PollPacketReader.popPacket].
*
- * @param reader a [TapPacketReader] to obtain incoming packets and reply to them.
+ * @param reader a [PollPacketReader] to obtain incoming packets and reply to them.
* @param packetFilter A filter to apply to incoming packets.
* @param name Name to use for the internal responder thread.
*/
abstract class PacketResponder(
- private val reader: TapPacketReader,
- private val packetFilter: Predicate<ByteArray>,
- name: String
+ private val reader: PollPacketReader,
+ private val packetFilter: Predicate<ByteArray>,
+ name: String
) {
private val replyThread = ReplyThread(name)
- protected abstract fun replyToPacket(packet: ByteArray, reader: TapPacketReader)
+ protected abstract fun replyToPacket(packet: ByteArray, reader: PollPacketReader)
/**
* Start the [PacketResponder].
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReader.java b/staticlibs/testutils/devicetests/com/android/testutils/PollPacketReader.java
similarity index 91%
rename from staticlibs/testutils/devicetests/com/android/testutils/TapPacketReader.java
rename to staticlibs/testutils/devicetests/com/android/testutils/PollPacketReader.java
index b25b9f2..dbc7eb0 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReader.java
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PollPacketReader.java
@@ -35,19 +35,19 @@
import kotlin.LazyKt;
/**
- * A packet reader that runs on a TAP interface.
+ * A packet reader that can poll for received packets and send responses on a fd.
*
* It also implements facilities to reply to received packets.
*/
-public class TapPacketReader extends PacketReader {
- private final FileDescriptor mTapFd;
+public class PollPacketReader extends PacketReader {
+ private final FileDescriptor mFd;
private final ArrayTrackRecord<byte[]> mReceivedPackets = new ArrayTrackRecord<>();
private final Lazy<ArrayTrackRecord<byte[]>.ReadHead> mReadHead =
LazyKt.lazy(mReceivedPackets::newReadHead);
- public TapPacketReader(Handler h, FileDescriptor tapFd, int maxPacketSize) {
+ public PollPacketReader(Handler h, FileDescriptor fd, int maxPacketSize) {
super(h, maxPacketSize);
- mTapFd = tapFd;
+ mFd = fd;
}
@@ -63,7 +63,7 @@
@Override
protected FileDescriptor createFd() {
- return mTapFd;
+ return mFd;
}
@Override
@@ -119,7 +119,7 @@
}
/*
- * Send a response on the TAP interface.
+ * Send a response on the fd.
*
* The passed ByteBuffer is flipped after use.
*
@@ -127,7 +127,7 @@
* @throws IOException if the interface can't be written to.
*/
public void sendResponse(final ByteBuffer packet) throws IOException {
- try (FileOutputStream out = new FileOutputStream(mTapFd)) {
+ try (FileOutputStream out = new FileOutputStream(mFd)) {
byte[] packetBytes = new byte[packet.limit()];
packet.get(packetBytes);
packet.flip(); // So we can reuse it in the future.
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java b/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java
index 51d57bc..6709555 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java
+++ b/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java
@@ -62,18 +62,18 @@
private static final String TAG = "RouterAdvertisementResponder";
private static final Inet6Address DNS_SERVER =
(Inet6Address) InetAddresses.parseNumericAddress("2001:4860:4860::64");
- private final TapPacketReader mPacketReader;
+ private final PollPacketReader mPacketReader;
// Maps IPv6 address to MacAddress and isRouter boolean.
private final Map<Inet6Address, Pair<MacAddress, Boolean>> mNeighborMap = new ArrayMap<>();
private final IpPrefix mPrefix;
- public RouterAdvertisementResponder(TapPacketReader packetReader, IpPrefix prefix) {
+ public RouterAdvertisementResponder(PollPacketReader packetReader, IpPrefix prefix) {
super(packetReader, RouterAdvertisementResponder::isRsOrNs, TAG);
mPacketReader = packetReader;
mPrefix = Objects.requireNonNull(prefix);
}
- public RouterAdvertisementResponder(TapPacketReader packetReader) {
+ public RouterAdvertisementResponder(PollPacketReader packetReader) {
this(packetReader, makeRandomPrefix());
}
@@ -148,7 +148,7 @@
buildSllaOption(srcMac));
}
- private static void sendResponse(TapPacketReader reader, ByteBuffer buffer) {
+ private static void sendResponse(PollPacketReader reader, ByteBuffer buffer) {
try {
reader.sendResponse(buffer);
} catch (IOException e) {
@@ -158,7 +158,7 @@
}
}
- private void replyToRouterSolicitation(TapPacketReader reader, MacAddress dstMac) {
+ private void replyToRouterSolicitation(PollPacketReader reader, MacAddress dstMac) {
for (Map.Entry<Inet6Address, Pair<MacAddress, Boolean>> it : mNeighborMap.entrySet()) {
final boolean isRouter = it.getValue().second;
if (!isRouter) {
@@ -169,7 +169,7 @@
}
}
- private void replyToNeighborSolicitation(TapPacketReader reader, MacAddress dstMac,
+ private void replyToNeighborSolicitation(PollPacketReader reader, MacAddress dstMac,
Inet6Address dstIp, Inet6Address targetIp) {
final Pair<MacAddress, Boolean> neighbor = mNeighborMap.get(targetIp);
if (neighbor == null) {
@@ -190,7 +190,7 @@
}
@Override
- protected void replyToPacket(byte[] packet, TapPacketReader reader) {
+ protected void replyToPacket(byte[] packet, PollPacketReader reader) {
final ByteBuffer buf = ByteBuffer.wrap(packet);
// Messages are filtered by parent class, so it is safe to assume that packet is either an
// RS or NS.
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt
index 701666c..adf7619 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt
@@ -31,9 +31,9 @@
private const val HANDLER_TIMEOUT_MS = 10_000L
/**
- * A [TestRule] that sets up a [TapPacketReader] on a [TestNetworkInterface] for use in the test.
+ * A [TestRule] that sets up a [PollPacketReader] on a [TestNetworkInterface] for use in the test.
*
- * @param maxPacketSize Maximum size of packets read in the [TapPacketReader] buffer.
+ * @param maxPacketSize Maximum size of packets read in the [PollPacketReader] buffer.
* @param autoStart Whether to initialize the interface and start the reader automatically for every
* test. If false, each test must either call start() and stop(), or be annotated
* with TapPacketReaderTest before using the reader or interface.
@@ -50,21 +50,21 @@
// referenced before they could be initialized (typically if autoStart is false and the test
// does not call start or use @TapPacketReaderTest).
lateinit var iface: TestNetworkInterface
- lateinit var reader: TapPacketReader
+ lateinit var reader: PollPacketReader
@Volatile
private var readerRunning = false
/**
* Indicates that the [TapPacketReaderRule] should initialize its [TestNetworkInterface] and
- * start the [TapPacketReader] before the test, and tear them down afterwards.
+ * start the [PollPacketReader] before the test, and tear them down afterwards.
*
* For use when [TapPacketReaderRule] is created with autoStart = false.
*/
annotation class TapPacketReaderTest
/**
- * Initialize the tap interface and start the [TapPacketReader].
+ * Initialize the tap interface and start the [PollPacketReader].
*
* Tests using this method must also call [stop] before exiting.
* @param handler Handler to run the reader on. Callers are responsible for safely terminating
@@ -85,13 +85,13 @@
}
val usedHandler = handler ?: HandlerThread(
TapPacketReaderRule::class.java.simpleName).apply { start() }.threadHandler
- reader = TapPacketReader(usedHandler, iface.fileDescriptor.fileDescriptor, maxPacketSize)
+ reader = PollPacketReader(usedHandler, iface.fileDescriptor.fileDescriptor, maxPacketSize)
reader.startAsyncForTest()
readerRunning = true
}
/**
- * Stop the [TapPacketReader].
+ * Stop the [PollPacketReader].
*
* Tests calling [start] must call this method before exiting. If a handler was specified in
* [start], all messages on that handler must also be processed after calling this method and
diff --git a/staticlibs/testutils/host/python/apf_test_base.py b/staticlibs/testutils/host/python/apf_test_base.py
index 9a30978..2552aa3 100644
--- a/staticlibs/testutils/host/python/apf_test_base.py
+++ b/staticlibs/testutils/host/python/apf_test_base.py
@@ -15,7 +15,7 @@
from mobly import asserts
from net_tests_utils.host.python import adb_utils, apf_utils, assert_utils, multi_devices_test_base, tether_utils
from net_tests_utils.host.python.tether_utils import UpstreamType
-
+import time
class ApfTestBase(multi_devices_test_base.MultiDevicesTestBase):
@@ -39,6 +39,7 @@
)
# Fetch device properties and storing them locally for later use.
+ # TODO: refactor to separate instances to store client and server device
self.server_iface_name, client_network = (
tether_utils.setup_hotspot_and_client_for_upstream_type(
self.serverDevice, self.clientDevice, UpstreamType.NONE
@@ -50,6 +51,21 @@
self.server_mac_address = apf_utils.get_hardware_address(
self.serverDevice, self.server_iface_name
)
+ self.client_mac_address = apf_utils.get_hardware_address(
+ self.clientDevice, self.client_iface_name
+ )
+ self.server_ipv4_addresses = apf_utils.get_ipv4_addresses(
+ self.serverDevice, self.server_iface_name
+ )
+ self.client_ipv4_addresses = apf_utils.get_ipv4_addresses(
+ self.clientDevice, self.client_iface_name
+ )
+ self.server_ipv6_addresses = apf_utils.get_ipv6_addresses(
+ self.serverDevice, self.server_iface_name
+ )
+ self.client_ipv6_addresses = apf_utils.get_ipv6_addresses(
+ self.clientDevice, self.client_iface_name
+ )
# Enable doze mode to activate APF.
adb_utils.set_doze_mode(self.clientDevice, True)
@@ -81,4 +97,19 @@
> count_before_test
)
- # TODO: Verify the packet is not actually received.
+ def send_packet_and_expect_reply_received(
+ self, send_packet: str, counter_name: str, receive_packet: str
+ ) -> None:
+ try:
+ apf_utils.start_capture_packets(self.serverDevice, self.server_iface_name)
+
+ self.send_packet_and_expect_counter_increased(send_packet, counter_name)
+
+ assert_utils.expect_with_retry(
+ lambda: apf_utils.get_matched_packet_counts(
+ self.serverDevice, self.server_iface_name, receive_packet
+ )
+ == 1
+ )
+ finally:
+ apf_utils.stop_capture_packets(self.serverDevice, self.server_iface_name)
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index c3330d2..7fe60bd 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -83,6 +83,69 @@
ad.log.debug("Getting apf counters: " + str(result))
return result
+def get_ipv4_addresses(
+ ad: android_device.AndroidDevice, iface_name: str
+) -> list[str]:
+ """Retrieves the IPv4 addresses of a given interface on an Android device.
+
+ This function executes an ADB shell command (`ip -4 address show`) to get the
+ network interface information and extracts the IPv4 address from the output.
+ If devices have no IPv4 address, raise PatternNotFoundException.
+
+ Args:
+ ad: The Android device object.
+ iface_name: The name of the network interface (e.g., "wlan0").
+
+ Returns:
+ The IPv4 addresses of the interface as a list of string.
+ Return empty list if no IPv4 address.
+ """
+ # output format:
+ # 54: wlan2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
+ # inet 192.168.195.162/24 brd 192.168.195.255 scope global wlan2
+ # valid_lft forever preferred_lft forever
+ # inet 192.168.1.1/24 brd 192.168.1.255 scope global wlan2
+ # valid_lft forever preferred_lft forever
+ output = adb_utils.adb_shell(ad, f"ip -4 address show {iface_name}")
+ pattern = r"inet\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/\d+"
+ matches = re.findall(pattern, output)
+
+ if matches:
+ return matches
+ else:
+ return []
+
+def get_ipv6_addresses(
+ ad: android_device.AndroidDevice, iface_name: str
+) -> list[str]:
+ """Retrieves the IPv6 addresses of a given interface on an Android device.
+
+ This function executes an ADB shell command (`ip -6 address show`) to get the
+ network interface information and extracts the IPv6 address from the output.
+ If devices have no IPv6 address, raise PatternNotFoundException.
+
+ Args:
+ ad: The Android device object.
+ iface_name: The name of the network interface (e.g., "wlan0").
+
+ Returns:
+ The IPv6 addresses of the interface as a list of string.
+ Return empty list if no IPv6 address.
+ """
+ # output format
+ # 54: wlan2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
+ # inet6 fe80::10a3:5dff:fe52:de32/64 scope link
+ # valid_lft forever preferred_lft forever
+ # inet6 fe80::1233:aadb:3d32:1234/64 scope link
+ # valid_lft forever preferred_lft forever
+ output = adb_utils.adb_shell(ad, f"ip -6 address show {iface_name}")
+ pattern = r"inet6\s+([0-9a-fA-F:]+)\/\d+"
+ matches = re.findall(pattern, output)
+
+ if matches:
+ return matches
+ else:
+ return []
def get_hardware_address(
ad: android_device.AndroidDevice, iface_name: str
@@ -115,6 +178,29 @@
"Cannot get hardware address for " + iface_name
)
+def is_packet_capture_supported(
+ ad: android_device.AndroidDevice,
+) -> bool:
+
+ # Invoke the shell command with empty argument and see how NetworkStack respond.
+ # If supported, an IllegalArgumentException with help page will be printed.
+ functions_with_args = (
+ # list all functions and args with (func, *args) tuple
+ (start_capture_packets, (ad, "")),
+ (stop_capture_packets, (ad, "")),
+ (get_matched_packet_counts, (ad, "", ""))
+ )
+
+ for func, args in functions_with_args:
+ try:
+ func(*args)
+ except UnsupportedOperationException:
+ return False
+ except Exception:
+ continue
+
+ # If no UnsupportOperationException is thrown, regard it as supported
+ return True
def is_send_raw_packet_downstream_supported(
ad: android_device.AndroidDevice,
@@ -161,25 +247,92 @@
representation of a packet starting from L2 header.
"""
- cmd = (
- "cmd network_stack send-raw-packet-downstream"
- f" {iface_name} {packet_in_hex}"
- )
+ cmd = f"cmd network_stack send-raw-packet-downstream {iface_name} {packet_in_hex}"
# Expect no output or Unknown command if NetworkStack is too old. Throw otherwise.
- try:
- output = adb_utils.adb_shell(ad, cmd)
- except AdbError as e:
- output = str(e.stdout)
- if output:
- if "Unknown command" in output:
- raise UnsupportedOperationException(
- "send-raw-packet-downstream command is not supported."
- )
+ adb_output = AdbOutputHandler(ad, cmd).get_output()
+ if adb_output:
raise assert_utils.UnexpectedBehaviorError(
- f"Got unexpected output: {output} for command: {cmd}."
+ f"Got unexpected output: {adb_output} for command: {cmd}."
)
+def start_capture_packets(
+ ad: android_device.AndroidDevice,
+ iface_name: str
+) -> None:
+ """Starts packet capturing on a specified network interface.
+
+ This function initiates packet capture on the given network interface of an
+ Android device using an ADB shell command. It handles potential errors
+ related to unsupported commands or unexpected output.
+ This command only supports downstream tethering interface.
+
+ Args:
+ ad: The Android device object.
+ iface_name: The name of the network interface (e.g., "wlan0").
+ """
+ cmd = f"cmd network_stack capture start {iface_name}"
+
+ # Expect no output or Unknown command if NetworkStack is too old. Throw otherwise.
+ adb_output = AdbOutputHandler(ad, cmd).get_output()
+ if adb_output != "success":
+ raise assert_utils.UnexpectedBehaviorError(
+ f"Got unexpected output: {adb_output} for command: {cmd}."
+ )
+
+def stop_capture_packets(
+ ad: android_device.AndroidDevice,
+ iface_name: str
+) -> None:
+ """Stops packet capturing on a specified network interface.
+
+ This function terminates packet capture on the given network interface of an
+ Android device using an ADB shell command. It handles potential errors
+ related to unsupported commands or unexpected output.
+
+ Args:
+ ad: The Android device object.
+ iface_name: The name of the network interface (e.g., "wlan0").
+ """
+ cmd = f"cmd network_stack capture stop {iface_name}"
+
+ # Expect no output or Unknown command if NetworkStack is too old. Throw otherwise.
+ adb_output = AdbOutputHandler(ad, cmd).get_output()
+ if adb_output != "success":
+ raise assert_utils.UnexpectedBehaviorError(
+ f"Got unexpected output: {adb_output} for command: {cmd}."
+ )
+
+def get_matched_packet_counts(
+ ad: android_device.AndroidDevice,
+ iface_name: str,
+ packet_in_hex: str
+) -> int:
+ """Gets the number of captured packets matching a specific hexadecimal pattern.
+
+ This function retrieves the count of captured packets on the specified
+ network interface that match a given hexadecimal pattern. It uses an ADB
+ shell command and handles potential errors related to unsupported commands,
+ unexpected output, or invalid output format.
+
+ Args:
+ ad: The Android device object.
+ iface_name: The name of the network interface (e.g., "wlan0").
+ packet_in_hex: The hexadecimal string representing the packet pattern.
+
+ Returns:
+ The number of matched packets as an integer.
+ """
+ cmd = f"cmd network_stack capture matched-packet-counts {iface_name} {packet_in_hex}"
+
+ # Expect no output or Unknown command if NetworkStack is too old. Throw otherwise.
+ adb_output = AdbOutputHandler(ad, cmd).get_output()
+ try:
+ return int(adb_output)
+ except ValueError as e:
+ raise assert_utils.UnexpectedBehaviorError(
+ f"Got unexpected exception: {e} for command: {cmd}."
+ )
@dataclass
class ApfCapabilities:
@@ -241,3 +394,19 @@
f"Supported apf version {caps.apf_version_supported} < expected version"
f" {expected_version}",
)
+
+class AdbOutputHandler:
+ def __init__(self, ad, cmd):
+ self._ad = ad
+ self._cmd = cmd
+
+ def get_output(self) -> str:
+ try:
+ return adb_utils.adb_shell(self._ad, self._cmd)
+ except AdbError as e:
+ output = str(e.stdout)
+ if "Unknown command" in output:
+ raise UnsupportedOperationException(
+ f"{self._cmd} is not supported."
+ )
+ return output
\ No newline at end of file
diff --git a/staticlibs/testutils/host/python/packet_utils.py b/staticlibs/testutils/host/python/packet_utils.py
new file mode 100644
index 0000000..b613f03
--- /dev/null
+++ b/staticlibs/testutils/host/python/packet_utils.py
@@ -0,0 +1,70 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from ipaddress import IPv4Address
+from socket import inet_aton
+
+ETHER_BROADCAST_MAC_ADDRESS = "FF:FF:FF:FF:FF:FF"
+ARP_REQUEST_OP = 1
+ARP_REPLY_OP = 2
+
+"""
+This variable defines a template for constructing ARP packets in hexadecimal format.
+It's used to provide the common fields for ARP packet, and replaced needed fields when constructing
+"""
+ARP_TEMPLATE = (
+ # Ether Header (14 bytes)
+ "{dst_mac}" + # DA
+ "{src_mac}" + # SA
+ "0806" + # ARP
+ # ARP Header (28 bytes)
+ "0001" + # Hardware type (Ethernet)
+ "0800" + # Protocol type (IPv4)
+ "06" + # hardware address length
+ "04" + # protocol address length
+ "{opcode}" + # opcode
+ "{sender_mac}" + # sender MAC
+ "{sender_ip}" + # sender IP
+ "{target_mac}" + # target MAC
+ "{target_ip}" # target IP
+)
+
+def construct_arp_packet(src_mac, dst_mac, src_ip, dst_ip, op) -> str:
+ """Constructs an ARP packet as a hexadecimal string.
+
+ This function creates an ARP packet by filling in the required fields
+ in a predefined ARP packet template.
+
+ Args:
+ src_mac: The MAC address of the sender. (e.g. "11:22:33:44:55:66")
+ dst_mac: The MAC address of the recipient. (e.g. "aa:bb:cc:dd:ee:ff")
+ src_ip: The IP address of the sender. (e.g. "1.1.1.1")
+ dst_ip: The IP address of the target machine. (e.g. "2.2.2.2")
+ op: The op code of the ARP packet, refer to ARP_*_OP
+
+ Returns:
+ A string representing the ARP packet in hexadecimal format.
+ """
+ # Replace the needed fields from packet template
+ arp_pkt = ARP_TEMPLATE.format(
+ dst_mac=dst_mac.replace(":",""),
+ src_mac=src_mac.replace(":",""),
+ opcode=str(op).rjust(4, "0"),
+ sender_mac=src_mac.replace(":",""),
+ sender_ip=inet_aton(src_ip).hex(),
+ target_mac=("000000000000" if op == ARP_REQUEST_OP else dst_mac.replace(":", "")),
+ target_ip=inet_aton(dst_ip).hex()
+ )
+
+ # always convert to upper case hex string
+ return arp_pkt.upper()
\ No newline at end of file
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt b/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
index 9f28234..dcd422c 100644
--- a/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
@@ -20,6 +20,7 @@
import com.android.testutils.FunctionalUtils.ThrowingRunnable
import com.android.testutils.FunctionalUtils.ThrowingSupplier
+import java.util.function.Consumer
import javax.annotation.CheckReturnValue
/**
@@ -73,11 +74,23 @@
* });
*/
+object TryTestConfig {
+ internal var diagnosticsCollector: Consumer<Throwable>? = null
+
+ /**
+ * Set the diagnostics collector to be used in case of failure in [tryTest].
+ */
+ fun setDiagnosticsCollector(collector: Consumer<Throwable>) {
+ diagnosticsCollector = collector
+ }
+}
+
@CheckReturnValue
fun <T> tryTest(block: () -> T) = TryExpr(
try {
Result.success(block())
} catch (e: Throwable) {
+ TryTestConfig.diagnosticsCollector?.accept(e)
Result.failure(e)
})
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index e95a81a..920492f 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -78,7 +78,7 @@
name: "ConnectivityCoverageTestsLib",
min_sdk_version: "30",
static_libs: [
- "FrameworksNetTestsLib",
+ "ConnectivityUnitTestsLib",
"NetdStaticLibTestsLib",
"NetworkStaticLibTestsLib",
"NetworkStackTestsLib",
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index 14d5d54..97be91a 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -12,17 +12,27 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-next_app_data = [":CtsHostsideNetworkTestsAppNext"]
-
-// The above line is put in place to prevent any future automerger merge conflict between aosp,
-// downstream branches. The CtsHostsideNetworkTestsAppNext target will not exist in
-// some downstream branches, but it should exist in aosp and some downstream branches.
-
package {
default_team: "trendy_team_fwk_core_networking",
default_applicable_licenses: ["Android-Apache-2.0"],
}
+java_defaults {
+ name: "CtsHostsideNetworkTestsAllAppDefaults",
+ platform_apis: true,
+ min_sdk_version: "30",
+ // Set target SDK to 10000 so that all the test helper apps are always subject to the most
+ // recent (and possibly most restrictive) target SDK behaviour. Also, this matches the target
+ // SDK of the tests themselves, and of other tests such as CtsNetTestCases.
+ // Note that some of the test helper apps (e.g., CtsHostsideNetworkCapTestsAppSdk33) override
+ // this with older SDK versions.
+ // Also note that unlike android_test targets, "current" does not work: the target SDK is set to
+ // something like "VanillaIceCream" instead of 100000. This means that the tests will not run on
+ // released devices with errors such as "Requires development platform VanillaIceCream but this
+ // is a release platform".
+ target_sdk_version: "10000",
+}
+
java_test_host {
name: "CtsHostsideNetworkTests",
defaults: ["cts_defaults"],
@@ -52,6 +62,6 @@
":CtsHostsideNetworkCapTestsAppWithoutProperty",
":CtsHostsideNetworkCapTestsAppWithProperty",
":CtsHostsideNetworkCapTestsAppSdk33",
- ] + next_app_data,
+ ],
per_testcase_directory: true,
}
diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp
index 2ca9adb..7fff1c2 100644
--- a/tests/cts/hostside/app/Android.bp
+++ b/tests/cts/hostside/app/Android.bp
@@ -19,9 +19,13 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-java_defaults {
- name: "CtsHostsideNetworkTestsAppDefaults",
- platform_apis: true,
+android_test_helper_app {
+ name: "CtsHostsideNetworkTestsApp",
+ defaults: [
+ "cts_support_defaults",
+ "framework-connectivity-test-defaults",
+ "CtsHostsideNetworkTestsAllAppDefaults",
+ ],
static_libs: [
"CtsHostsideNetworkTestsAidl",
"androidx.test.ext.junit",
@@ -39,35 +43,4 @@
srcs: [
"src/**/*.java",
],
- // Tag this module as a cts test artifact
- test_suites: [
- "general-tests",
- "sts",
- ],
- min_sdk_version: "30",
-}
-
-android_test_helper_app {
- name: "CtsHostsideNetworkTestsApp",
- defaults: [
- "cts_support_defaults",
- "framework-connectivity-test-defaults",
- "CtsHostsideNetworkTestsAppDefaults",
- ],
- static_libs: [
- "NetworkStackApiStableShims",
- ],
-}
-
-android_test_helper_app {
- name: "CtsHostsideNetworkTestsAppNext",
- defaults: [
- "cts_support_defaults",
- "framework-connectivity-test-defaults",
- "CtsHostsideNetworkTestsAppDefaults",
- "ConnectivityNextEnableDefaults",
- ],
- static_libs: [
- "NetworkStackApiCurrentShims",
- ],
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
index fe522a0..a39a8d0 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
@@ -29,9 +29,6 @@
import android.util.Pair;
import com.android.modules.utils.build.SdkLevel;
-import com.android.networkstack.apishim.VpnServiceBuilderShimImpl;
-import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
-import com.android.networkstack.apishim.common.VpnServiceBuilderShim;
import com.android.testutils.PacketReflector;
import java.io.IOException;
@@ -102,8 +99,7 @@
}
private void start(String packageName, Intent intent) {
- Builder builder = new Builder();
- VpnServiceBuilderShim vpnServiceBuilderShim = VpnServiceBuilderShimImpl.newInstance();
+ VpnService.Builder builder = new VpnService.Builder();
final String addresses = parseIpAndMaskListArgument(packageName, intent, "addresses",
builder::addAddress);
@@ -112,11 +108,7 @@
if (SdkLevel.isAtLeastT() && intent.getBooleanExtra(packageName + ".addRoutesByIpPrefix",
false)) {
addedRoutes = parseIpPrefixListArgument(packageName, intent, "routes", (prefix) -> {
- try {
- vpnServiceBuilderShim.addRoute(builder, prefix);
- } catch (UnsupportedApiLevelException e) {
- throw new RuntimeException(e);
- }
+ builder.addRoute(prefix);
});
} else {
addedRoutes = parseIpAndMaskListArgument(packageName, intent, "routes",
@@ -127,11 +119,7 @@
if (SdkLevel.isAtLeastT()) {
excludedRoutes = parseIpPrefixListArgument(packageName, intent, "excludedRoutes",
(prefix) -> {
- try {
- vpnServiceBuilderShim.excludeRoute(builder, prefix);
- } catch (UnsupportedApiLevelException e) {
- throw new RuntimeException(e);
- }
+ builder.excludeRoute(prefix);
});
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index d7631eb..d05a8d0 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -20,8 +20,11 @@
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.READ_DEVICE_CONFIG;
import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
+import static android.content.Context.RECEIVER_EXPORTED;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
import static android.content.pm.PackageManager.FEATURE_WIFI;
+import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
@@ -46,9 +49,6 @@
import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_RESUMED;
import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_STARTED;
import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_STOPPED;
-import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
-import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
-import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_EXPORTED;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.RecorderCallback.CallbackEntry.BLOCKED_STATUS_INT;
import static com.android.testutils.TestPermissionUtil.runAsShell;
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index cb55c7b..05abcdd 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -21,20 +21,14 @@
android_test_helper_app {
name: "CtsHostsideNetworkTestsApp2",
- defaults: ["cts_support_defaults"],
- platform_apis: true,
+ defaults: [
+ "cts_support_defaults",
+ "CtsHostsideNetworkTestsAllAppDefaults",
+ ],
static_libs: [
"androidx.annotation_annotation",
"CtsHostsideNetworkTestsAidl",
- "NetworkStackApiStableShims",
],
srcs: ["src/**/*.java"],
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- "sts",
- ],
sdk_version: "test_current",
- min_sdk_version: "30",
}
diff --git a/tests/cts/hostside/networkslicingtestapp/Android.bp b/tests/cts/hostside/networkslicingtestapp/Android.bp
index 79ad2e2..0eed51c 100644
--- a/tests/cts/hostside/networkslicingtestapp/Android.bp
+++ b/tests/cts/hostside/networkslicingtestapp/Android.bp
@@ -21,7 +21,6 @@
java_defaults {
name: "CtsHostsideNetworkCapTestsAppDefaults",
- platform_apis: true,
static_libs: [
"androidx.test.ext.junit",
"androidx.test.rules",
@@ -29,13 +28,6 @@
"cts-net-utils",
],
srcs: ["src/**/*.java"],
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- "sts",
- ],
- min_sdk_version: "30",
}
android_test_helper_app {
@@ -43,6 +35,7 @@
defaults: [
"cts_support_defaults",
"CtsHostsideNetworkCapTestsAppDefaults",
+ "CtsHostsideNetworkTestsAllAppDefaults",
],
manifest: "AndroidManifestWithoutProperty.xml",
sdk_version: "test_current",
@@ -53,6 +46,7 @@
defaults: [
"cts_support_defaults",
"CtsHostsideNetworkCapTestsAppDefaults",
+ "CtsHostsideNetworkTestsAllAppDefaults",
],
manifest: "AndroidManifestWithProperty.xml",
sdk_version: "test_current",
@@ -63,6 +57,7 @@
defaults: [
"cts_support_defaults",
"CtsHostsideNetworkCapTestsAppDefaults",
+ "CtsHostsideNetworkTestsAllAppDefaults",
],
target_sdk_version: "33",
manifest: "AndroidManifestWithoutProperty.xml",
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index 69d61b3..e222ff6 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import com.android.modules.utils.build.testing.DeviceSdkLevel;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.targetprep.BuildError;
@@ -36,17 +35,13 @@
abstract class HostsideNetworkTestCase extends BaseHostJUnit4Test {
protected static final String TEST_PKG = "com.android.cts.net.hostside";
protected static final String TEST_APK = "CtsHostsideNetworkTestsApp.apk";
- protected static final String TEST_APK_NEXT = "CtsHostsideNetworkTestsAppNext.apk";
protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
protected static final String TEST_APP2_APK = "CtsHostsideNetworkTestsApp2.apk";
@BeforeClassWithInfo
public static void setUpOnceBase(TestInformation testInfo) throws Exception {
- DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(testInfo.getDevice());
- String testApk = deviceSdkLevel.isDeviceAtLeastV() ? TEST_APK_NEXT : TEST_APK;
-
uninstallPackage(testInfo, TEST_PKG, false);
- installPackage(testInfo, testApk);
+ installPackage(testInfo, TEST_APK);
}
@AfterClassWithInfo
diff --git a/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
index fa68e3e..ae572e6 100644
--- a/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
+++ b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
@@ -166,6 +166,8 @@
*/
@Test
public void testRouterSolicitations() throws Exception {
+ assumeTrue(new DeviceSdkLevel(mDevice).isDeviceAtLeastU());
+
for (String interfaceDir : mSysctlDirs) {
String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "router_solicitations";
int value = readIntFromPath(path);
@@ -186,8 +188,7 @@
*/
@Test
public void testCongestionControl() throws Exception {
- final DeviceSdkLevel dsl = new DeviceSdkLevel(mDevice);
- assumeTrue(dsl.isDeviceAtLeastV());
+ assumeTrue(new DeviceSdkLevel(mDevice).isDeviceAtLeastV());
String path = "/proc/sys/net/ipv4/tcp_congestion_control";
String value = mDevice.executeAdbCommand("shell", "cat", path).trim();
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 24431a6..a65316f 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -56,7 +56,13 @@
the runner will only run the tests annotated with that annotation, but if it does not,
the runner will run all the tests. -->
<option name="include-annotation" value="com.android.testutils.filters.{MODULE}" />
+ <option name="device-listeners" value="com.android.testutils.ConnectivityDiagnosticsCollector" />
</test>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <!-- Pattern matching the fileKey used by ConnectivityDiagnosticsCollector when calling addFileMetric -->
+ <option name="pull-pattern-keys" value="com.android.testutils.ConnectivityDiagnosticsCollector.*"/>
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
<!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
one of the Mainline modules below is present on the device used for testing. -->
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index b62db04..0e9ea0c 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -3061,6 +3061,7 @@
try {
final Network cellNetwork = networkCallbackRule.requestCell();
+ ensureCellIsValidatedBeforeMockingValidationUrls();
final Network wifiNetwork = prepareValidatedNetwork();
final TestableNetworkCallback defaultCb =
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index 041e6cb..1de4cf9 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -71,7 +71,7 @@
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.RouterAdvertisementResponder
import com.android.testutils.SC_V2
-import com.android.testutils.TapPacketReader
+import com.android.testutils.PollPacketReader
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnDscpPolicyStatusUpdated
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
@@ -135,7 +135,7 @@
private lateinit var srcAddressV6: Inet6Address
private lateinit var iface: TestNetworkInterface
private lateinit var tunNetworkCallback: TestNetworkCallback
- private lateinit var reader: TapPacketReader
+ private lateinit var reader: PollPacketReader
private lateinit var arpResponder: ArpResponder
private lateinit var raResponder: RouterAdvertisementResponder
@@ -169,7 +169,7 @@
}
handlerThread.start()
- reader = TapPacketReader(
+ reader = PollPacketReader(
handlerThread.threadHandler,
iface.fileDescriptor.fileDescriptor,
MAX_PACKET_LENGTH)
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 61ebd8f..1e2a212 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -72,7 +72,7 @@
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.RouterAdvertisementResponder
-import com.android.testutils.TapPacketReader
+import com.android.testutils.PollPacketReader
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.assertThrows
import com.android.testutils.runAsShell
@@ -151,7 +151,7 @@
hasCarrier: Boolean
) {
private val tapInterface: TestNetworkInterface
- private val packetReader: TapPacketReader
+ private val packetReader: PollPacketReader
private val raResponder: RouterAdvertisementResponder
private val tnm: TestNetworkManager
val name get() = tapInterface.interfaceName
@@ -169,7 +169,11 @@
tnm.createTapInterface(hasCarrier, false /* bringUp */)
}
val mtu = tapInterface.mtu
- packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu)
+ packetReader = PollPacketReader(
+ handler,
+ tapInterface.fileDescriptor.fileDescriptor,
+ mtu
+ )
raResponder = RouterAdvertisementResponder(packetReader)
val iidString = "fe80::${Integer.toHexString(Random().nextInt(65536))}"
val linklocal = InetAddresses.parseNumericAddress(iidString) as Inet6Address
@@ -336,7 +340,7 @@
}
}
- private fun isEthernetSupported() : Boolean {
+ private fun isEthernetSupported(): Boolean {
return context.getSystemService(EthernetManager::class.java) != null
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index 2315940..11fc6df 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -115,6 +115,8 @@
private static final int NETWORK_TAG = 0xf00d;
private static final long THRESHOLD_BYTES = 2 * 1024 * 1024; // 2 MB
+ private static final long SHORT_TOLERANCE = MINUTE / 2;
+ private static final long LONG_TOLERANCE = MINUTE * 120;
private abstract class NetworkInterfaceToTest {
private boolean mMetered;
@@ -364,16 +366,17 @@
}
}
- private boolean shouldTestThisNetworkType(int networkTypeIndex, final long tolerance)
+ private boolean shouldTestThisNetworkType(int networkTypeIndex) {
+ return mPm.hasSystemFeature(mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature());
+ }
+
+ private void requestNetworkAndGenerateTraffic(int networkTypeIndex, final long tolerance)
throws Exception {
- boolean hasFeature = mPm.hasSystemFeature(
- mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature());
- if (!hasFeature) {
- return false;
- }
- NetworkCallback callback = new NetworkCallback(tolerance, new URL(CHECK_CONNECTIVITY_URL));
+ final NetworkInterfaceToTest networkInterface = mNetworkInterfacesToTest[networkTypeIndex];
+ final NetworkCallback callback = new NetworkCallback(tolerance,
+ new URL(CHECK_CONNECTIVITY_URL));
mCm.requestNetwork(new NetworkRequest.Builder()
- .addTransportType(mNetworkInterfacesToTest[networkTypeIndex].getTransportType())
+ .addTransportType(networkInterface.getTransportType())
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build(), callback);
synchronized (this) {
@@ -388,20 +391,17 @@
}
}
mCm.unregisterNetworkCallback(callback);
- if (callback.success) {
- mNetworkInterfacesToTest[networkTypeIndex].setMetered(callback.metered);
- mNetworkInterfacesToTest[networkTypeIndex].setRoaming(callback.roaming);
- mNetworkInterfacesToTest[networkTypeIndex].setIsDefault(callback.isDefault);
- return true;
+ if (!callback.success) {
+ fail(networkInterface.getSystemFeature()
+ + " is a reported system feature, however no corresponding "
+ + "connected network interface was found or the attempt "
+ + "to connect and read has timed out (timeout = " + (TIMEOUT_MILLIS * 2.4)
+ + "ms)." + networkInterface.getErrorMessage());
}
- // This will always fail at this point as we know 'hasFeature' is true.
- assertFalse(mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature()
- + " is a reported system feature, "
- + "however no corresponding connected network interface was found or the attempt "
- + "to connect and read has timed out (timeout = " + (TIMEOUT_MILLIS * 2) + "ms)."
- + mNetworkInterfacesToTest[networkTypeIndex].getErrorMessage(), hasFeature);
- return false;
+ networkInterface.setMetered(callback.metered);
+ networkInterface.setRoaming(callback.roaming);
+ networkInterface.setIsDefault(callback.isDefault);
}
private String getSubscriberId(int networkIndex) {
@@ -417,9 +417,10 @@
@Test
public void testDeviceSummary() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
- if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+ if (!shouldTestThisNetworkType(i)) {
continue;
}
+ requestNetworkAndGenerateTraffic(i, SHORT_TOLERANCE);
setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
NetworkStats.Bucket bucket = null;
try {
@@ -453,9 +454,10 @@
@Test
public void testUserSummary() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
- if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+ if (!shouldTestThisNetworkType(i)) {
continue;
}
+ requestNetworkAndGenerateTraffic(i, SHORT_TOLERANCE);
setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
NetworkStats.Bucket bucket = null;
try {
@@ -489,14 +491,15 @@
@Test
public void testAppSummary() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ if (!shouldTestThisNetworkType(i)) {
+ continue;
+ }
// Use tolerance value that large enough to make sure stats of at
// least one bucket is included. However, this is possible that
// the test will see data of different app but with the same UID
// that created before testing.
// TODO: Consider query stats before testing and use the difference to verify.
- if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
- continue;
- }
+ requestNetworkAndGenerateTraffic(i, LONG_TOLERANCE);
setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
NetworkStats result = null;
try {
@@ -565,10 +568,11 @@
@Test
public void testAppDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
- // Relatively large tolerance to accommodate for history bucket size.
- if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ if (!shouldTestThisNetworkType(i)) {
continue;
}
+ // Relatively large tolerance to accommodate for history bucket size.
+ requestNetworkAndGenerateTraffic(i, LONG_TOLERANCE);
setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
NetworkStats result = null;
try {
@@ -609,9 +613,10 @@
public void testUidDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Relatively large tolerance to accommodate for history bucket size.
- if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ if (!shouldTestThisNetworkType(i)) {
continue;
}
+ requestNetworkAndGenerateTraffic(i, LONG_TOLERANCE);
setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
NetworkStats result = null;
try {
@@ -663,9 +668,10 @@
public void testTagDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Relatively large tolerance to accommodate for history bucket size.
- if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ if (!shouldTestThisNetworkType(i)) {
continue;
}
+ requestNetworkAndGenerateTraffic(i, LONG_TOLERANCE);
setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
NetworkStats result = null;
try {
@@ -769,10 +775,11 @@
@Test
public void testUidTagStateDetails() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
- // Relatively large tolerance to accommodate for history bucket size.
- if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ if (!shouldTestThisNetworkType(i)) {
continue;
}
+ // Relatively large tolerance to accommodate for history bucket size.
+ requestNetworkAndGenerateTraffic(i, LONG_TOLERANCE);
setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
NetworkStats result = null;
try {
@@ -847,9 +854,10 @@
public void testCallback() throws Exception {
for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
// Relatively large tolerance to accommodate for history bucket size.
- if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+ if (!shouldTestThisNetworkType(i)) {
continue;
}
+ requestNetworkAndGenerateTraffic(i, SHORT_TOLERANCE);
setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
TestUsageCallback usageCallback = new TestUsageCallback();
diff --git a/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt b/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
index f9acb66..aad072c 100644
--- a/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
@@ -46,7 +46,7 @@
import com.android.testutils.DhcpClientPacketFilter
import com.android.testutils.DhcpOptionFilter
import com.android.testutils.RecorderCallback.CallbackEntry
-import com.android.testutils.TapPacketReader
+import com.android.testutils.PollPacketReader
import com.android.testutils.TestHttpServer
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.runAsShell
@@ -93,7 +93,7 @@
private val ethRequestCb = TestableNetworkCallback()
private lateinit var iface: TestNetworkInterface
- private lateinit var reader: TapPacketReader
+ private lateinit var reader: PollPacketReader
private lateinit var capportUrl: Uri
private var testSkipped = false
@@ -118,7 +118,7 @@
iface = testInterfaceRule.createTapInterface()
handlerThread.start()
- reader = TapPacketReader(
+ reader = PollPacketReader(
handlerThread.threadHandler,
iface.fileDescriptor.fileDescriptor,
MAX_PACKET_LENGTH)
@@ -218,7 +218,7 @@
TEST_MTU, false /* rapidCommit */, capportUrl.toString())
}
-private fun <T : DhcpPacket> TapPacketReader.assertDhcpPacketReceived(
+private fun <T : DhcpPacket> PollPacketReader.assertDhcpPacketReceived(
packetType: Class<T>,
timeoutMs: Long,
type: Byte
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index c71d925..ad6fe63 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -100,7 +100,7 @@
import com.android.testutils.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.UnregisterCallbackSucceeded
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
-import com.android.testutils.TapPacketReader
+import com.android.testutils.PollPacketReader
import com.android.testutils.TestDnsPacket
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
@@ -1299,10 +1299,10 @@
val si = makeTestServiceInfo(testNetwork1.network)
- val packetReader = TapPacketReader(
- Handler(handlerThread.looper),
+ val packetReader = PollPacketReader(
+ Handler(handlerThread.looper),
testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1345,10 +1345,10 @@
parseNumericAddress("2001:db8::3"))
}
- val packetReader = TapPacketReader(
- Handler(handlerThread.looper),
+ val packetReader = PollPacketReader(
+ Handler(handlerThread.looper),
testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1391,10 +1391,10 @@
hostname = customHostname
}
- val packetReader = TapPacketReader(
- Handler(handlerThread.looper),
+ val packetReader = PollPacketReader(
+ Handler(handlerThread.looper),
testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1438,10 +1438,10 @@
val registrationRecord = NsdRegistrationRecord()
val discoveryRecord = NsdDiscoveryRecord()
val registeredService = registerService(registrationRecord, si)
- val packetReader = TapPacketReader(
- Handler(handlerThread.looper),
+ val packetReader = PollPacketReader(
+ Handler(handlerThread.looper),
testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1518,10 +1518,10 @@
val registrationRecord = NsdRegistrationRecord()
val discoveryRecord = NsdDiscoveryRecord()
val registeredService = registerService(registrationRecord, si)
- val packetReader = TapPacketReader(
+ val packetReader = PollPacketReader(
Handler(handlerThread.looper),
testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1587,10 +1587,10 @@
val registrationRecord = NsdRegistrationRecord()
val discoveryRecord = NsdDiscoveryRecord()
val registeredService = registerService(registrationRecord, si)
- val packetReader = TapPacketReader(
- Handler(handlerThread.looper),
+ val packetReader = PollPacketReader(
+ Handler(handlerThread.looper),
testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1630,10 +1630,10 @@
fun testDiscoveryWithPtrOnlyResponse_ServiceIsFound() {
// Register service on testNetwork1
val discoveryRecord = NsdDiscoveryRecord()
- val packetReader = TapPacketReader(
- Handler(handlerThread.looper),
+ val packetReader = PollPacketReader(
+ Handler(handlerThread.looper),
testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1688,10 +1688,10 @@
fun testResolveWhenServerSendsNoAdditionalRecord() {
// Resolve service on testNetwork1
val resolveRecord = NsdResolveRecord()
- val packetReader = TapPacketReader(
- Handler(handlerThread.looper),
+ val packetReader = PollPacketReader(
+ Handler(handlerThread.looper),
testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1776,8 +1776,8 @@
var nsResponder: NSResponder? = null
tryTest {
registerService(registrationRecord, si)
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = PollPacketReader(Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1826,7 +1826,7 @@
var nsResponder: NSResponder? = null
tryTest {
registerService(registrationRecord, si)
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
+ val packetReader = PollPacketReader(Handler(handlerThread.looper),
testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
packetReader.startAsyncForTest()
@@ -1916,7 +1916,7 @@
var nsResponder: NSResponder? = null
tryTest {
registerService(registrationRecord, si)
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
+ val packetReader = PollPacketReader(Handler(handlerThread.looper),
testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
packetReader.startAsyncForTest()
@@ -1991,10 +1991,10 @@
// Register service on testNetwork1
val discoveryRecord = NsdDiscoveryRecord()
- val packetReader = TapPacketReader(
- Handler(handlerThread.looper),
+ val packetReader = PollPacketReader(
+ Handler(handlerThread.looper),
testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -2355,10 +2355,10 @@
it.port = TEST_PORT
it.publicKey = publicKey
}
- val packetReader = TapPacketReader(
- Handler(handlerThread.looper),
+ val packetReader = PollPacketReader(
+ Handler(handlerThread.looper),
testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -2410,10 +2410,10 @@
parseNumericAddress("2001:db8::2"))
it.publicKey = publicKey
}
- val packetReader = TapPacketReader(
- Handler(handlerThread.looper),
+ val packetReader = PollPacketReader(
+ Handler(handlerThread.looper),
testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -2467,10 +2467,10 @@
it.hostAddresses = listOf()
it.publicKey = publicKey
}
- val packetReader = TapPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
+ val packetReader = PollPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -2582,10 +2582,10 @@
"test_nsd_avoid_advertising_empty_txt_records",
"1"
)
- val packetReader = TapPacketReader(
- Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor,
- 1500 /* maxPacketSize */
+ val packetReader = PollPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
diff --git a/tests/cts/net/util/java/android/net/cts/util/EthernetTestInterface.kt b/tests/cts/net/util/java/android/net/cts/util/EthernetTestInterface.kt
index 32d6899..20cfa1d 100644
--- a/tests/cts/net/util/java/android/net/cts/util/EthernetTestInterface.kt
+++ b/tests/cts/net/util/java/android/net/cts/util/EthernetTestInterface.kt
@@ -28,7 +28,7 @@
import android.os.Handler
import android.util.Log
import com.android.net.module.util.ArrayTrackRecord
-import com.android.testutils.TapPacketReader
+import com.android.testutils.PollPacketReader
import com.android.testutils.runAsShell
import com.android.testutils.waitForIdle
import java.net.NetworkInterface
@@ -85,7 +85,7 @@
assertNotNull(nif)
return nif.mtu
}
- val packetReader = TapPacketReader(handler, testIface.fileDescriptor.fileDescriptor, mtu)
+ val packetReader = PollPacketReader(handler, testIface.fileDescriptor.fileDescriptor, mtu)
private val listener = EthernetStateListener(name)
private val em = context.getSystemService(EthernetManager::class.java)!!
@Volatile private var cleanedUp = false
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index 1165018..83818be 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -94,14 +94,8 @@
// Tag this module as a cts test artifact
test_suites: [
"cts",
- "mts-dnsresolver",
- "mts-networking",
"mts-tethering",
- "mts-wifi",
- "mcts-dnsresolver",
- "mcts-networking",
"mcts-tethering",
- "mcts-wifi",
"general-tests",
],
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 00f9d05..6892a42 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -72,8 +72,8 @@
],
}
-java_defaults {
- name: "FrameworksNetTestsDefaults",
+android_library {
+ name: "ConnectivityUnitTestsLib",
min_sdk_version: "30",
defaults: [
"framework-connectivity-internal-test-defaults",
@@ -82,6 +82,7 @@
"java/**/*.java",
"java/**/*.kt",
],
+ exclude_srcs: [":non-connectivity-module-test"],
static_libs: [
"androidx.test.rules",
"androidx.test.uiautomator_uiautomator",
@@ -110,14 +111,6 @@
"ServiceConnectivityResources",
],
exclude_kotlinc_generated_files: false,
-}
-
-android_library {
- name: "FrameworksNetTestsLib",
- defaults: [
- "FrameworksNetTestsDefaults",
- ],
- exclude_srcs: [":non-connectivity-module-test"],
visibility: ["//packages/modules/Connectivity/tests:__subpackages__"],
}
@@ -137,7 +130,7 @@
java_genrule {
name: "frameworks-net-tests-lib-jarjar-gen",
tool_files: [
- ":FrameworksNetTestsLib{.jar}",
+ ":ConnectivityUnitTestsLib{.jar}",
"jarjar-excludes.txt",
],
tools: [
@@ -145,7 +138,7 @@
],
out: ["frameworks-net-tests-lib-jarjar-rules.txt"],
cmd: "$(location jarjar-rules-generator) " +
- "$(location :FrameworksNetTestsLib{.jar}) " +
+ "$(location :ConnectivityUnitTestsLib{.jar}) " +
"--prefix android.net.connectivity " +
"--excludes $(location jarjar-excludes.txt) " +
"--output $(out)",
@@ -156,14 +149,25 @@
name: "FrameworksNetTests",
enabled: enable_frameworks_net_tests,
defaults: [
- "FrameworksNetTestsDefaults",
+ "framework-connectivity-internal-test-defaults",
"FrameworksNetTests-jni-defaults",
],
jarjar_rules: ":frameworks-net-tests-jarjar-rules",
+ srcs: [":non-connectivity-module-test"],
test_suites: ["device-tests"],
static_libs: [
+ "frameworks-base-testutils",
"services.core",
"services.net",
+ "androidx.test.rules",
+ "framework-protos",
+ "mockito-target-minus-junit4",
+ "net-tests-utils",
+ "service-connectivity-pre-jarjar",
+ "service-connectivity-tiramisu-pre-jarjar",
+ ],
+ libs: [
+ "android.test.mock.stubs",
],
jni_libs: [
"libandroid_net_connectivity_com_android_net_module_util_jni",
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 d801fba..ab2fb99 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -499,7 +500,7 @@
verify(executorProvider).shutdownExecutorService(mockExecutorService);
verify(mockServiceTypeClientType1Network1).stopSendAndReceive(mockListenerOne);
verify(socketClient).stopDiscovery();
- verify(mockServiceCache).removeServices(cacheKey);
+ verify(mockServiceCache, timeout(DEFAULT_TIMEOUT)).removeServices(cacheKey);
}
@Test
@@ -523,7 +524,7 @@
runOnHandler(() -> callback.onSocketDestroyed(SOCKET_KEY_NETWORK_1));
verify(mockServiceTypeClientType1Network1).notifySocketDestroyed();
verify(executorProvider).shutdownExecutorService(mockExecutorService);
- verify(mockServiceCache).removeServices(cacheKey);
+ verify(mockServiceCache, timeout(DEFAULT_TIMEOUT)).removeServices(cacheKey);
}
private MdnsPacket createMdnsPacket(String serviceType) {
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
index 5c3ad22..efae244 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
@@ -22,21 +22,27 @@
import com.android.server.connectivity.mdns.MdnsConstants.FLAG_TRUNCATED
import com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR
import com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR
+import com.android.server.connectivity.mdns.MdnsInetAddressRecord
import com.android.server.connectivity.mdns.MdnsPacket
import com.android.server.connectivity.mdns.MdnsPacketReader
import com.android.server.connectivity.mdns.MdnsPointerRecord
import com.android.server.connectivity.mdns.MdnsRecord
+import com.android.server.connectivity.mdns.MdnsResponse
+import com.android.server.connectivity.mdns.MdnsServiceInfo
+import com.android.server.connectivity.mdns.MdnsServiceRecord
+import com.android.server.connectivity.mdns.MdnsTextRecord
import com.android.server.connectivity.mdns.util.MdnsUtils.createQueryDatagramPackets
import com.android.server.connectivity.mdns.util.MdnsUtils.truncateServiceName
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
-import java.net.DatagramPacket
-import kotlin.test.assertContentEquals
+import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
+import java.net.DatagramPacket
+import kotlin.test.assertContentEquals
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -157,4 +163,54 @@
assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v6Packet, otherV6Packet)))
assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet, v6Packet)))
}
+
+ @Test
+ fun testBuildMdnsServiceInfoFromResponse() {
+ val serviceInstanceName = "MyTestService"
+ val serviceType = "_testservice._tcp.local"
+ val hostName = "Android_000102030405060708090A0B0C0D0E0F.local"
+ val port = 12345
+ val ttlTime = 120000L
+ val testElapsedRealtime = 123L
+ val serviceName = "$serviceInstanceName.$serviceType".split(".").toTypedArray()
+ val v4Address = "192.0.2.1"
+ val v6Address = "2001:db8::1"
+ val interfaceIndex = 99
+ val response = MdnsResponse(0 /* now */, serviceName, interfaceIndex, null /* network */)
+ // Set PTR record
+ response.addPointerRecord(MdnsPointerRecord(serviceType.split(".").toTypedArray(),
+ testElapsedRealtime, false /* cacheFlush */, ttlTime, serviceName))
+ // Set SRV record.
+ response.serviceRecord = MdnsServiceRecord(serviceName, testElapsedRealtime,
+ false /* cacheFlush */, ttlTime, 0 /* servicePriority */, 0 /* serviceWeight */,
+ port, hostName.split(".").toTypedArray())
+ // Set TXT record.
+ response.textRecord = MdnsTextRecord(serviceName,
+ testElapsedRealtime, true /* cacheFlush */, 0L /* ttlMillis */,
+ listOf(MdnsServiceInfo.TextEntry.fromString("somedifferent=entry")))
+ // Set InetAddress record.
+ response.addInet4AddressRecord(MdnsInetAddressRecord(hostName.split(".").toTypedArray(),
+ testElapsedRealtime, true /* cacheFlush */,
+ 0L /* ttlMillis */, InetAddresses.parseNumericAddress(v4Address)))
+ response.addInet6AddressRecord(MdnsInetAddressRecord(hostName.split(".").toTypedArray(),
+ testElapsedRealtime, true /* cacheFlush */,
+ 0L /* ttlMillis */, InetAddresses.parseNumericAddress(v6Address)))
+
+ // Convert a MdnsResponse to a MdnsServiceInfo
+ val serviceInfo = MdnsUtils.buildMdnsServiceInfoFromResponse(
+ response, serviceType.split(".").toTypedArray(), testElapsedRealtime)
+
+ assertEquals(serviceInstanceName, serviceInfo.serviceInstanceName)
+ assertArrayEquals(serviceType.split(".").toTypedArray(), serviceInfo.serviceType)
+ assertArrayEquals(hostName.split(".").toTypedArray(), serviceInfo.hostName)
+ assertEquals(port, serviceInfo.port)
+ assertEquals(1, serviceInfo.ipv4Addresses.size)
+ assertEquals(v4Address, serviceInfo.ipv4Addresses[0])
+ assertEquals(1, serviceInfo.ipv6Addresses.size)
+ assertEquals(v6Address, serviceInfo.ipv6Addresses[0])
+ assertEquals(interfaceIndex, serviceInfo.interfaceIndex)
+ assertEquals(null, serviceInfo.network)
+ assertEquals(mapOf("somedifferent" to "entry"),
+ serviceInfo.attributes)
+ }
}
diff --git a/thread/demoapp/java/com/android/threadnetwork/demoapp/ThreadNetworkSettingsFragment.java b/thread/demoapp/java/com/android/threadnetwork/demoapp/ThreadNetworkSettingsFragment.java
index e95feaf..2659d24 100644
--- a/thread/demoapp/java/com/android/threadnetwork/demoapp/ThreadNetworkSettingsFragment.java
+++ b/thread/demoapp/java/com/android/threadnetwork/demoapp/ThreadNetworkSettingsFragment.java
@@ -47,6 +47,9 @@
import java.time.Duration;
import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Timer;
+import java.util.TimerTask;
import java.util.concurrent.Executor;
public final class ThreadNetworkSettingsFragment extends Fragment {
@@ -59,11 +62,16 @@
private TextView mTextState;
private TextView mTextNetworkInfo;
private TextView mMigrateNetworkState;
+ private TextView mEphemeralKeyStateText;
private Executor mMainExecutor;
private int mDeviceRole;
private long mPartitionId;
private ActiveOperationalDataset mActiveDataset;
+ private int mEphemeralKeyState;
+ private String mEphemeralKey;
+ private Instant mEphemeralKeyExpiry;
+ private Timer mEphemeralKeyLifetimeTimer;
private static final byte[] DEFAULT_ACTIVE_DATASET_TLVS =
base16().lowerCase()
@@ -89,6 +97,19 @@
}
}
+ private static String ephemeralKeyStateToString(int ephemeralKeyState) {
+ switch (ephemeralKeyState) {
+ case ThreadNetworkController.EPHEMERAL_KEY_DISABLED:
+ return "Disabled";
+ case ThreadNetworkController.EPHEMERAL_KEY_ENABLED:
+ return "Enabled";
+ case ThreadNetworkController.EPHEMERAL_KEY_IN_USE:
+ return "Connected";
+ default:
+ return "Unknown";
+ }
+ }
+
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -144,6 +165,15 @@
ThreadNetworkSettingsFragment.this.mPartitionId = mPartitionId;
updateState();
}
+
+ @Override
+ public void onEphemeralKeyStateChanged(
+ int state, String ephemeralKey, Instant expiry) {
+ ThreadNetworkSettingsFragment.this.mEphemeralKeyState = state;
+ ThreadNetworkSettingsFragment.this.mEphemeralKey = ephemeralKey;
+ ThreadNetworkSettingsFragment.this.mEphemeralKeyExpiry = expiry;
+ updateState();
+ }
});
mThreadController.registerOperationalDatasetCallback(
mMainExecutor,
@@ -155,6 +185,7 @@
mTextState = (TextView) view.findViewById(R.id.text_state);
mTextNetworkInfo = (TextView) view.findViewById(R.id.text_network_info);
+ mEphemeralKeyStateText = (TextView) view.findViewById(R.id.text_ephemeral_key_state);
if (mThreadController == null) {
mTextState.setText("Thread not supported!");
@@ -168,6 +199,11 @@
((Button) view.findViewById(R.id.button_migrate_network))
.setOnClickListener(v -> doMigration());
+ ((Button) view.findViewById(R.id.button_activate_ephemeral_key_mode))
+ .setOnClickListener(v -> doActivateEphemeralKeyMode());
+ ((Button) view.findViewById(R.id.button_deactivate_ephemeral_key_mode))
+ .setOnClickListener(v -> doDeactivateEphemeralKeyMode());
+
updateState();
}
@@ -234,12 +270,46 @@
});
}
+ private void doActivateEphemeralKeyMode() {
+ mThreadController.activateEphemeralKeyMode(
+ Duration.ofMinutes(2),
+ mMainExecutor,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onError(ThreadNetworkException error) {
+ Log.e(TAG, "Failed to activate ephemeral key", error);
+ }
+
+ @Override
+ public void onResult(Void v) {
+ Log.i(TAG, "Successfully activated ephemeral key mode");
+ }
+ });
+ }
+
+ private void doDeactivateEphemeralKeyMode() {
+ mThreadController.deactivateEphemeralKeyMode(
+ mMainExecutor,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onError(ThreadNetworkException error) {
+ Log.e(TAG, "Failed to deactivate ephemeral key", error);
+ }
+
+ @Override
+ public void onResult(Void v) {
+ Log.i(TAG, "Successfully deactivated ephemeral key mode");
+ }
+ });
+ }
+
private void updateState() {
Log.i(
TAG,
String.format(
- "Updating Thread states (mDeviceRole: %s)",
- deviceRoleToString(mDeviceRole)));
+ "Updating Thread states (mDeviceRole: %s, mEphemeralKeyState: %s)",
+ deviceRoleToString(mDeviceRole),
+ ephemeralKeyStateToString(mEphemeralKeyState)));
String state =
String.format(
@@ -254,6 +324,30 @@
? base16().encode(mActiveDataset.getExtendedPanId())
: null);
mTextState.setText(state);
+
+ updateEphemeralKeyStatus();
+ }
+
+ private void updateEphemeralKeyStatus() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(ephemeralKeyStateToString(mEphemeralKeyState));
+ if (mEphemeralKeyState != ThreadNetworkController.EPHEMERAL_KEY_DISABLED) {
+ sb.append("\nPasscode: ");
+ sb.append(mEphemeralKey);
+ sb.append("\nRemaining lifetime: ");
+ sb.append(Instant.now().until(mEphemeralKeyExpiry, ChronoUnit.SECONDS));
+ sb.append(" seconds");
+ mEphemeralKeyLifetimeTimer = new Timer();
+ mEphemeralKeyLifetimeTimer.schedule(
+ new TimerTask() {
+ @Override
+ public void run() {
+ mMainExecutor.execute(() -> updateEphemeralKeyStatus());
+ }
+ },
+ 1000L /* delay in millis */);
+ }
+ mEphemeralKeyStateText.setText(sb.toString());
}
private void updateNetworkInfo(LinkProperties linProperties) {
diff --git a/thread/demoapp/res/layout/thread_network_settings_fragment.xml b/thread/demoapp/res/layout/thread_network_settings_fragment.xml
index cae46a3..84d984b 100644
--- a/thread/demoapp/res/layout/thread_network_settings_fragment.xml
+++ b/thread/demoapp/res/layout/thread_network_settings_fragment.xml
@@ -14,58 +14,86 @@
limitations under the License.
-->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="8dp"
- android:orientation="vertical"
- tools:context=".ThreadNetworkSettingsFragment" >
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="8dp"
+ android:orientation="vertical"
+ tools:context=".ThreadNetworkSettingsFragment" >
- <Button android:id="@+id/button_join_network"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Join Network" />
- <Button android:id="@+id/button_leave_network"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Leave Network" />
+ <Button android:id="@+id/button_join_network"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Join Network" />
+ <Button android:id="@+id/button_leave_network"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Leave Network" />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="16dp"
- android:textStyle="bold"
- android:text="State" />
- <TextView
- android:id="@+id/text_state"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="12dp"
- android:typeface="monospace" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="16dp"
+ android:textStyle="bold"
+ android:text="State" />
+ <TextView
+ android:id="@+id/text_state"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="12dp"
+ android:typeface="monospace" />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:textSize="16dp"
- android:textStyle="bold"
- android:text="Network Info" />
- <TextView
- android:id="@+id/text_network_info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="12dp" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:textSize="16dp"
+ android:textStyle="bold"
+ android:text="Network Info" />
+ <TextView
+ android:id="@+id/text_network_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="12dp" />
- <Button android:id="@+id/button_migrate_network"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Migrate Network" />
- <TextView
- android:id="@+id/text_migrate_network_state"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="12dp" />
-</LinearLayout>
+ <Button android:id="@+id/button_migrate_network"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Migrate Network" />
+ <TextView
+ android:id="@+id/text_migrate_network_state"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="12dp" />
+
+ <Button android:id="@+id/button_activate_ephemeral_key_mode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Activate Ephemeral Key Mode" />
+ <Button android:id="@+id/button_deactivate_ephemeral_key_mode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Deactivate Ephemeral Key Mode" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:textSize="16dp"
+ android:textStyle="bold"
+ android:text="Ephemeral Key State" />
+ <TextView
+ android:id="@+id/text_ephemeral_key_state"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="50dp"
+ android:textSize="12dp" />
+ </LinearLayout>
+</ScrollView>
diff --git a/thread/framework/java/android/net/thread/IStateCallback.aidl b/thread/framework/java/android/net/thread/IStateCallback.aidl
index 9d0a571..d074b01 100644
--- a/thread/framework/java/android/net/thread/IStateCallback.aidl
+++ b/thread/framework/java/android/net/thread/IStateCallback.aidl
@@ -23,4 +23,6 @@
void onDeviceRoleChanged(int deviceRole);
void onPartitionIdChanged(long partitionId);
void onThreadEnableStateChanged(int enabledState);
+ void onEphemeralKeyStateChanged(
+ int ephemeralKeyState, @nullable String ephemeralKey, long lifetimeMillis);
}
diff --git a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
index b7f68c9..e9cbb83 100644
--- a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
+++ b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
@@ -53,4 +53,7 @@
void setConfiguration(in ThreadConfiguration config, in IOperationReceiver receiver);
void registerConfigurationCallback(in IConfigurationReceiver receiver);
void unregisterConfigurationCallback(in IConfigurationReceiver receiver);
+
+ void activateEphemeralKeyMode(long lifetimeMillis, in IOperationReceiver receiver);
+ void deactivateEphemeralKeyMode(in IOperationReceiver receiver);
}
diff --git a/thread/framework/java/android/net/thread/ThreadConfiguration.java b/thread/framework/java/android/net/thread/ThreadConfiguration.java
index 1c25535..e6fa1ef 100644
--- a/thread/framework/java/android/net/thread/ThreadConfiguration.java
+++ b/thread/framework/java/android/net/thread/ThreadConfiguration.java
@@ -126,18 +126,29 @@
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_SET_NAT64_CONFIGURATION_ENABLED)
+ @SystemApi
public static final class Builder {
private boolean mNat64Enabled = false;
private boolean mDhcpv6PdEnabled = false;
- /** Creates a new {@link Builder} object with all features disabled. */
+ /**
+ * Creates a new {@link Builder} object with all features disabled.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SET_NAT64_CONFIGURATION_ENABLED)
+ @SystemApi
public Builder() {}
/**
* Creates a new {@link Builder} object from a {@link ThreadConfiguration} object.
*
* @param config the Border Router configurations to be copied
+ * @hide
*/
+ @FlaggedApi(Flags.FLAG_SET_NAT64_CONFIGURATION_ENABLED)
+ @SystemApi
public Builder(@NonNull ThreadConfiguration config) {
Objects.requireNonNull(config);
@@ -150,7 +161,11 @@
*
* <p>Enabling this feature will allow Thread devices to connect to the internet/cloud over
* IPv4.
+ *
+ * @hide
*/
+ @FlaggedApi(Flags.FLAG_SET_NAT64_CONFIGURATION_ENABLED)
+ @SystemApi
@NonNull
public Builder setNat64Enabled(boolean enabled) {
this.mNat64Enabled = enabled;
@@ -162,6 +177,8 @@
*
* <p>Enabling this feature will allow Thread devices to connect to the internet/cloud over
* IPv6.
+ *
+ * @hide
*/
@NonNull
public Builder setDhcpv6PdEnabled(boolean enabled) {
@@ -169,7 +186,13 @@
return this;
}
- /** Creates a new {@link ThreadConfiguration} object. */
+ /**
+ * Creates a new {@link ThreadConfiguration} object.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SET_NAT64_CONFIGURATION_ENABLED)
+ @SystemApi
@NonNull
public ThreadConfiguration build() {
return new ThreadConfiguration(this);
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java
index cb4e8de..14d22d1 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -40,6 +40,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
+import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
@@ -82,6 +83,25 @@
/** The Thread radio is being disabled. */
public static final int STATE_DISABLING = 2;
+ /** The ephemeral key mode is disabled. */
+ @FlaggedApi(Flags.FLAG_EPSKC_ENABLED)
+ public static final int EPHEMERAL_KEY_DISABLED = 0;
+
+ /**
+ * The ephemeral key mode is enabled, an external commissioner candidate can use the ephemeral
+ * key to connect to this device and get Thread credential shared.
+ */
+ @FlaggedApi(Flags.FLAG_EPSKC_ENABLED)
+ public static final int EPHEMERAL_KEY_ENABLED = 1;
+
+ /**
+ * The ephemeral key is in use. This state means there is already an active secure session
+ * connected to this device with the ephemeral key, it's not possible to use the ephemeral key
+ * for new connections in this state.
+ */
+ @FlaggedApi(Flags.FLAG_EPSKC_ENABLED)
+ public static final int EPHEMERAL_KEY_IN_USE = 2;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -100,6 +120,13 @@
value = {STATE_DISABLED, STATE_ENABLED, STATE_DISABLING})
public @interface EnabledState {}
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"EPHEMERAL_KEY_"},
+ value = {EPHEMERAL_KEY_DISABLED, EPHEMERAL_KEY_ENABLED, EPHEMERAL_KEY_IN_USE})
+ public @interface EphemeralKeyState {}
+
/** Thread standard version 1.3. */
public static final int THREAD_VERSION_1_3 = 4;
@@ -110,6 +137,9 @@
@SuppressLint("MinMaxConstant")
public static final int MAX_POWER_CHANNEL_DISABLED = Integer.MIN_VALUE;
+ /** The maximum lifetime of an ephemeral key. @hide */
+ @NonNull private static final Duration EPHEMERAL_KEY_LIFETIME_MAX = Duration.ofMinutes(10);
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({THREAD_VERSION_1_3})
@@ -174,6 +204,87 @@
}
}
+ /** Returns the maximum lifetime allowed when activating ephemeral key mode. */
+ @FlaggedApi(Flags.FLAG_EPSKC_ENABLED)
+ @NonNull
+ public Duration getMaxEphemeralKeyLifetime() {
+ return EPHEMERAL_KEY_LIFETIME_MAX;
+ }
+
+ /**
+ * Activates ephemeral key mode with a given {@code lifetime}. The ephemeral key is a temporary,
+ * single-use numeric code that is used for Thread Administration Sharing. After activation, the
+ * mode may expire or get deactivated, caller to this method should subscribe to the ephemeral
+ * key state updates with {@link #registerStateCallback} to get notified when the ephemeral key
+ * state changes.
+ *
+ * <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called. The ephemeral
+ * key string contains a sequence of numeric digits 0-9 of user-input friendly length (typically
+ * 9). Subscribers to ephemeral key state updates with {@link #registerStateCallback} will be
+ * notified with a call to {@link #onEphemeralKeyStateChanged}.
+ *
+ * <p>On failure, {@link OutcomeReceiver#onError} of {@code receiver} will be invoked with a
+ * specific error:
+ *
+ * <ul>
+ * <li>{@link ThreadNetworkException#ERROR_FAILED_PRECONDITION} when this device is not
+ * attached to Thread network
+ * <li>{@link ThreadNetworkException#ERROR_BUSY} when ephemeral key mode is already activated
+ * on the device, caller can recover from this error when the ephemeral key mode gets
+ * deactivated
+ * </ul>
+ *
+ * @param lifetime valid lifetime of the generated ephemeral key, should be larger than {@link
+ * Duration#ZERO} and at most the duration returned by {@link #getMaxEphemeralKeyLifetime}.
+ * @param executor the executor on which to execute {@code receiver}
+ * @param receiver the receiver to receive the result of this operation
+ * @throws IllegalArgumentException if the {@code lifetime} exceeds the allowed range
+ */
+ @FlaggedApi(Flags.FLAG_EPSKC_ENABLED)
+ @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
+ public void activateEphemeralKeyMode(
+ @NonNull Duration lifetime,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
+ if (lifetime.compareTo(Duration.ZERO) <= 0
+ || lifetime.compareTo(EPHEMERAL_KEY_LIFETIME_MAX) > 0) {
+ throw new IllegalArgumentException(
+ "Invalid ephemeral key lifetime: the value must be in range of (0, "
+ + EPHEMERAL_KEY_LIFETIME_MAX
+ + "]");
+ }
+ long lifetimeMillis = lifetime.toMillis();
+ try {
+ mControllerService.activateEphemeralKeyMode(
+ lifetimeMillis, new OperationReceiverProxy(executor, receiver));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Deactivates ephemeral key mode. If there is an active connection with the ephemeral key, the
+ * connection will be terminated.
+ *
+ * <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called. The call will
+ * always succeed if the device is not in ephemeral key mode.
+ *
+ * @param executor the executor to execute {@code receiver}
+ * @param receiver the receiver to receive the result of this operation
+ */
+ @FlaggedApi(Flags.FLAG_EPSKC_ENABLED)
+ @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
+ public void deactivateEphemeralKeyMode(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
+ try {
+ mControllerService.deactivateEphemeralKeyMode(
+ new OperationReceiverProxy(executor, receiver));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** Returns the Thread version this device is operating on. */
@ThreadVersion
public int getThreadVersion() {
@@ -248,6 +359,24 @@
* @param enabledState the new Thread enabled state
*/
default void onThreadEnableStateChanged(@EnabledState int enabledState) {}
+
+ /**
+ * The ephemeral key state has changed.
+ *
+ * @param ephemeralKeyState the ephemeral key state
+ * @param ephemeralKey the ephemeral key string which contains a sequence of numeric digits
+ * 0-9 of user-input friendly length (typically 9), or {@code null} if {@code
+ * ephemeralKeyState} is {@link #EPHEMERAL_KEY_DISABLED} or the caller doesn't have the
+ * permission {@link android.permission.THREAD_NETWORK_PRIVILEGED}
+ * @param expiry a timestamp of when the ephemeral key will expire or {@code null} if {@code
+ * ephemeralKeyState} is {@link #EPHEMERAL_KEY_DISABLED}
+ */
+ @FlaggedApi(Flags.FLAG_EPSKC_ENABLED)
+ @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
+ default void onEphemeralKeyStateChanged(
+ @EphemeralKeyState int ephemeralKeyState,
+ @Nullable String ephemeralKey,
+ @Nullable Instant expiry) {}
}
private static final class StateCallbackProxy extends IStateCallback.Stub {
@@ -288,13 +417,34 @@
Binder.restoreCallingIdentity(identity);
}
}
+
+ @Override
+ public void onEphemeralKeyStateChanged(
+ @EphemeralKeyState int ephemeralKeyState,
+ String ephemeralKey,
+ long lifetimeMillis) {
+ final long identity = Binder.clearCallingIdentity();
+ final Instant expiry =
+ ephemeralKeyState == EPHEMERAL_KEY_DISABLED
+ ? null
+ : Instant.now().plusMillis(lifetimeMillis);
+
+ try {
+ mExecutor.execute(
+ () ->
+ mCallback.onEphemeralKeyStateChanged(
+ ephemeralKeyState, ephemeralKey, expiry));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
/**
* Registers a callback to be called when Thread network states are changed.
*
- * <p>Upon return of this method, methods of {@code callback} will be invoked immediately with
- * existing states.
+ * <p>Upon return of this method, all methods of {@code callback} will be invoked immediately
+ * with existing states. The order of the invoked callbacks is not guaranteed.
*
* @param executor the executor to execute the {@code callback}
* @param callback the callback to receive Thread network state changes
@@ -595,15 +745,19 @@
* OutcomeReceiver#onResult} will be called, and the {@code configuration} will be applied and
* persisted to the device; the configuration changes can be observed by {@link
* #registerConfigurationCallback}. On failure, {@link OutcomeReceiver#onError} of {@code
- * receiver} will be invoked with a specific error.
+ * receiver} will be invoked with a specific error:
+ *
+ * <ul>
+ * <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_FEATURE} the configuration enables a
+ * feature which is not supported by the platform.
+ * </ul>
*
* @param configuration the configuration to set
* @param executor the executor to execute {@code receiver}
* @param receiver the receiver to receive result of this operation
- * @hide
*/
- // @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
- // @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
+ @FlaggedApi(Flags.FLAG_SET_NAT64_CONFIGURATION_ENABLED)
+ @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
public void setConfiguration(
@NonNull ThreadConfiguration configuration,
@NonNull @CallbackExecutor Executor executor,
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index f7da644..4e812fb 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -15,6 +15,7 @@
package com.android.server.thread;
import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.MulticastRoutingConfig.CONFIG_FORWARD_NONE;
import static android.net.MulticastRoutingConfig.FORWARD_SELECTED;
import static android.net.MulticastRoutingConfig.FORWARD_WITH_MIN_SCOPE;
@@ -26,6 +27,7 @@
import static android.net.thread.ActiveOperationalDataset.MESH_LOCAL_PREFIX_FIRST_BYTE;
import static android.net.thread.ActiveOperationalDataset.SecurityPolicy.DEFAULT_ROTATION_TIME_HOURS;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_DETACHED;
+import static android.net.thread.ThreadNetworkController.EPHEMERAL_KEY_DISABLED;
import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
import static android.net.thread.ThreadNetworkController.STATE_DISABLING;
import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
@@ -218,7 +220,6 @@
private boolean mUserRestricted;
private boolean mForceStopOtDaemonEnabled;
- private OtDaemonConfiguration mOtDaemonConfig;
private InfraLinkState mInfraLinkState;
@VisibleForTesting
@@ -247,7 +248,6 @@
// TODO: networkToLinkProperties should be shared with NsdPublisher, add a test/assert to
// verify they are the same.
mNetworkToLinkProperties = networkToLinkProperties;
- mOtDaemonConfig = new OtDaemonConfiguration.Builder().build();
mInfraLinkState = new InfraLinkState.Builder().build();
mPersistentSettings = persistentSettings;
mNsdPublisher = nsdPublisher;
@@ -344,6 +344,7 @@
otDaemon.initialize(
mTunIfController.getTunFd(),
shouldEnableThread(),
+ newOtDaemonConfig(mPersistentSettings.getConfiguration()),
mNsdPublisher,
getMeshcopTxtAttributes(mResources.get()),
mOtDaemonCallbackProxy,
@@ -554,22 +555,21 @@
public void setConfiguration(
@NonNull ThreadConfiguration configuration, @NonNull IOperationReceiver receiver) {
enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
- mHandler.post(() -> setConfigurationInternal(configuration, receiver));
+ mHandler.post(
+ () ->
+ setConfigurationInternal(
+ configuration, new OperationReceiverWrapper(receiver)));
}
private void setConfigurationInternal(
@NonNull ThreadConfiguration configuration,
- @NonNull IOperationReceiver operationReceiver) {
+ @NonNull OperationReceiverWrapper receiver) {
checkOnHandlerThread();
LOG.i("Set Thread configuration: " + configuration);
final boolean changed = mPersistentSettings.putConfiguration(configuration);
- try {
- operationReceiver.onSuccess();
- } catch (RemoteException e) {
- // do nothing if the client is dead
- }
+ receiver.onSuccess();
if (changed) {
for (IConfigurationReceiver configReceiver : mConfigurationReceivers.keySet()) {
try {
@@ -579,7 +579,22 @@
}
}
}
- // TODO: set the configuration at ot-daemon
+ try {
+ getOtDaemon()
+ .setConfiguration(
+ newOtDaemonConfig(configuration),
+ new LoggingOtStatusReceiver("setConfiguration"));
+ } catch (RemoteException | ThreadNetworkException e) {
+ LOG.e("otDaemon.setConfiguration failed. Config: " + configuration, e);
+ }
+ }
+
+ private static OtDaemonConfiguration newOtDaemonConfig(
+ @NonNull ThreadConfiguration threadConfig) {
+ return new OtDaemonConfiguration.Builder()
+ .setNat64Enabled(threadConfig.isNat64Enabled())
+ .setDhcpv6PdEnabled(threadConfig.isDhcpv6PdEnabled())
+ .build();
}
@Override
@@ -762,19 +777,17 @@
+ ", localNetworkInfo: "
+ localNetworkInfo
+ "}");
- if (localNetworkInfo.getUpstreamNetwork() == null) {
+ mUpstreamNetwork = localNetworkInfo.getUpstreamNetwork();
+ if (mUpstreamNetwork == null) {
setInfraLinkState(newInfraLinkStateBuilder().build());
return;
}
- if (!localNetworkInfo.getUpstreamNetwork().equals(mUpstreamNetwork)) {
- mUpstreamNetwork = localNetworkInfo.getUpstreamNetwork();
- if (mNetworkToLinkProperties.containsKey(mUpstreamNetwork)) {
- setInfraLinkState(
- newInfraLinkStateBuilder(mNetworkToLinkProperties.get(mUpstreamNetwork))
- .build());
- }
- mNsdPublisher.setNetworkForHostResolution(mUpstreamNetwork);
+ if (mNetworkToLinkProperties.containsKey(mUpstreamNetwork)) {
+ setInfraLinkState(
+ newInfraLinkStateBuilder(mNetworkToLinkProperties.get(mUpstreamNetwork))
+ .build());
}
+ mNsdPublisher.setNetworkForHostResolution(mUpstreamNetwork);
}
}
@@ -855,6 +868,47 @@
}
@Override
+ public void activateEphemeralKeyMode(long lifetimeMillis, IOperationReceiver receiver) {
+ enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
+
+ mHandler.post(
+ () ->
+ activateEphemeralKeyModeInternal(
+ lifetimeMillis, new OperationReceiverWrapper(receiver)));
+ }
+
+ private void activateEphemeralKeyModeInternal(
+ long lifetimeMillis, OperationReceiverWrapper receiver) {
+ checkOnHandlerThread();
+
+ try {
+ getOtDaemon().activateEphemeralKeyMode(lifetimeMillis, newOtStatusReceiver(receiver));
+ } catch (RemoteException | ThreadNetworkException e) {
+ LOG.e("otDaemon.activateEphemeralKeyMode failed", e);
+ receiver.onError(e);
+ }
+ }
+
+ @Override
+ public void deactivateEphemeralKeyMode(IOperationReceiver receiver) {
+ enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
+
+ mHandler.post(
+ () -> deactivateEphemeralKeyModeInternal(new OperationReceiverWrapper(receiver)));
+ }
+
+ private void deactivateEphemeralKeyModeInternal(OperationReceiverWrapper receiver) {
+ checkOnHandlerThread();
+
+ try {
+ getOtDaemon().deactivateEphemeralKeyMode(newOtStatusReceiver(receiver));
+ } catch (RemoteException | ThreadNetworkException e) {
+ LOG.e("otDaemon.deactivateEphemeralKeyMode failed", e);
+ receiver.onError(e);
+ }
+ }
+
+ @Override
public void createRandomizedDataset(
String networkName, IActiveOperationalDatasetReceiver receiver) {
ActiveOperationalDatasetReceiverWrapper receiverWrapper =
@@ -1003,7 +1057,14 @@
@Override
public void registerStateCallback(IStateCallback stateCallback) throws RemoteException {
enforceAllPermissionsGranted(permission.ACCESS_NETWORK_STATE);
- mHandler.post(() -> mOtDaemonCallbackProxy.registerStateCallback(stateCallback));
+ boolean hasThreadPrivilegedPermission =
+ (mContext.checkCallingOrSelfPermission(PERMISSION_THREAD_NETWORK_PRIVILEGED)
+ == PERMISSION_GRANTED);
+
+ mHandler.post(
+ () ->
+ mOtDaemonCallbackProxy.registerStateCallback(
+ stateCallback, hasThreadPrivilegedPermission));
}
@Override
@@ -1258,19 +1319,15 @@
}
private void setInfraLinkState(InfraLinkState newInfraLinkState) {
- if (mInfraLinkState.equals(newInfraLinkState)) {
- return;
+ if (!Objects.equals(mInfraLinkState, newInfraLinkState)) {
+ LOG.i("Infra link state changed: " + mInfraLinkState + " -> " + newInfraLinkState);
}
- LOG.i("Infra link state changed: " + mInfraLinkState + " -> " + newInfraLinkState);
-
setInfraLinkInterfaceName(newInfraLinkState.interfaceName);
+ setInfraLinkNat64Prefix(newInfraLinkState.nat64Prefix);
mInfraLinkState = newInfraLinkState;
}
private void setInfraLinkInterfaceName(String newInfraLinkInterfaceName) {
- if (Objects.equals(mInfraLinkState.interfaceName, newInfraLinkInterfaceName)) {
- return;
- }
ParcelFileDescriptor infraIcmp6Socket = null;
if (newInfraLinkInterfaceName != null) {
try {
@@ -1290,6 +1347,16 @@
}
}
+ private void setInfraLinkNat64Prefix(@Nullable String newNat64Prefix) {
+ try {
+ getOtDaemon()
+ .setInfraLinkNat64Prefix(
+ newNat64Prefix, new LoggingOtStatusReceiver("setInfraLinkNat64Prefix"));
+ } catch (RemoteException | ThreadNetworkException e) {
+ LOG.e("Failed to set infra link NAT64 prefix " + newNat64Prefix, e);
+ }
+ }
+
private void handleThreadInterfaceStateChanged(boolean isUp) {
try {
mTunIfController.setInterfaceUp(isUp);
@@ -1413,11 +1480,6 @@
return builder.build();
}
- private static OtDaemonConfiguration.Builder newOtDaemonConfigBuilder(
- OtDaemonConfiguration config) {
- return new OtDaemonConfiguration.Builder();
- }
-
private static InfraLinkState.Builder newInfraLinkStateBuilder() {
return new InfraLinkState.Builder().setInterfaceName("");
}
@@ -1427,7 +1489,13 @@
if (linkProperties == null) {
return newInfraLinkStateBuilder();
}
- return new InfraLinkState.Builder().setInterfaceName(linkProperties.getInterfaceName());
+ String nat64Prefix = null;
+ if (linkProperties.getNat64Prefix() != null) {
+ nat64Prefix = linkProperties.getNat64Prefix().toString();
+ }
+ return new InfraLinkState.Builder()
+ .setInterfaceName(linkProperties.getInterfaceName())
+ .setNat64Prefix(nat64Prefix);
}
private static final class CallbackMetadata {
@@ -1438,9 +1506,13 @@
final IBinder.DeathRecipient deathRecipient;
- CallbackMetadata(IBinder.DeathRecipient deathRecipient) {
+ final boolean hasThreadPrivilegedPermission;
+
+ CallbackMetadata(
+ IBinder.DeathRecipient deathRecipient, boolean hasThreadPrivilegedPermission) {
this.id = allocId();
this.deathRecipient = deathRecipient;
+ this.hasThreadPrivilegedPermission = hasThreadPrivilegedPermission;
}
private static long allocId() {
@@ -1482,7 +1554,8 @@
private ActiveOperationalDataset mActiveDataset;
private PendingOperationalDataset mPendingDataset;
- public void registerStateCallback(IStateCallback callback) {
+ public void registerStateCallback(
+ IStateCallback callback, boolean hasThreadPrivilegedPermission) {
checkOnHandlerThread();
if (mStateCallbacks.containsKey(callback)) {
throw new IllegalStateException("Registering the same IStateCallback twice");
@@ -1490,7 +1563,8 @@
IBinder.DeathRecipient deathRecipient =
() -> mHandler.post(() -> unregisterStateCallback(callback));
- CallbackMetadata callbackMetadata = new CallbackMetadata(deathRecipient);
+ CallbackMetadata callbackMetadata =
+ new CallbackMetadata(deathRecipient, hasThreadPrivilegedPermission);
mStateCallbacks.put(callback, callbackMetadata);
try {
callback.asBinder().linkToDeath(deathRecipient, 0);
@@ -1523,7 +1597,8 @@
IBinder.DeathRecipient deathRecipient =
() -> mHandler.post(() -> unregisterDatasetCallback(callback));
- CallbackMetadata callbackMetadata = new CallbackMetadata(deathRecipient);
+ CallbackMetadata callbackMetadata =
+ new CallbackMetadata(deathRecipient, true /* hasThreadPrivilegedPermission */);
mOpDatasetCallbacks.put(callback, callbackMetadata);
try {
callback.asBinder().linkToDeath(deathRecipient, 0);
@@ -1602,16 +1677,18 @@
}
@Override
- public void onStateChanged(OtDaemonState newState, long listenerId) {
+ public void onStateChanged(@NonNull OtDaemonState newState, long listenerId) {
mHandler.post(() -> onStateChangedInternal(newState, listenerId));
}
private void onStateChangedInternal(OtDaemonState newState, long listenerId) {
checkOnHandlerThread();
+
onInterfaceStateChanged(newState.isInterfaceUp);
onDeviceRoleChanged(newState.deviceRole, listenerId);
onPartitionIdChanged(newState.partitionId, listenerId);
onThreadEnabledChanged(newState.threadEnabled, listenerId);
+ onEphemeralKeyStateChanged(newState, listenerId);
mState = newState;
ActiveOperationalDataset newActiveDataset;
@@ -1687,6 +1764,43 @@
}
}
+ private void onEphemeralKeyStateChanged(OtDaemonState newState, long listenerId) {
+ checkOnHandlerThread();
+ boolean hasChange = isEphemeralKeyStateChanged(mState, newState);
+
+ for (var callbackEntry : mStateCallbacks.entrySet()) {
+ if (!hasChange && callbackEntry.getValue().id != listenerId) {
+ continue;
+ }
+ String passcode =
+ callbackEntry.getValue().hasThreadPrivilegedPermission
+ ? newState.ephemeralKeyPasscode
+ : null;
+ if (newState.ephemeralKeyState == EPHEMERAL_KEY_DISABLED) {
+ passcode = null;
+ }
+ try {
+ callbackEntry
+ .getKey()
+ .onEphemeralKeyStateChanged(
+ newState.ephemeralKeyState,
+ passcode,
+ newState.ephemeralKeyLifetimeMillis);
+ } catch (RemoteException ignored) {
+ // do nothing if the client is dead
+ }
+ }
+ }
+
+ private static boolean isEphemeralKeyStateChanged(
+ OtDaemonState oldState, @NonNull OtDaemonState newState) {
+ if (oldState == null) return true;
+ if (oldState.ephemeralKeyState != newState.ephemeralKeyState) return true;
+ if (oldState.ephemeralKeyState == EPHEMERAL_KEY_DISABLED) return false;
+ return (!Objects.equals(oldState.ephemeralKeyPasscode, newState.ephemeralKeyPasscode)
+ || oldState.ephemeralKeyLifetimeMillis != newState.ephemeralKeyLifetimeMillis);
+ }
+
private void onActiveOperationalDatasetChanged(
ActiveOperationalDataset activeDataset, long listenerId) {
checkOnHandlerThread();
diff --git a/thread/tests/cts/AndroidTest.xml b/thread/tests/cts/AndroidTest.xml
index 34aabe2..e954d3b 100644
--- a/thread/tests/cts/AndroidTest.xml
+++ b/thread/tests/cts/AndroidTest.xml
@@ -56,4 +56,14 @@
<!-- Ignores tests introduced by guava-android-testlib -->
<option name="exclude-annotation" value="org.junit.Ignore"/>
</test>
+
+ <!--
+ This doesn't override a read-only flag, to run the tests locally with `epskc_enabled` flag
+ enabled, set the flag to `is_fixed_read_only: false`. This should be removed after the
+ `epskc_enabled` flag is rolled out.
+ -->
+ <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer">
+ <option name="flag-value"
+ value="thread_network/com.android.net.thread.flags.epskc_enabled=true"/>
+ </target_preparer>
</configuration>
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index c048394..d9ce9e1 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -27,11 +27,14 @@
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_ROUTER;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
+import static android.net.thread.ThreadNetworkController.EPHEMERAL_KEY_DISABLED;
+import static android.net.thread.ThreadNetworkController.EPHEMERAL_KEY_ENABLED;
import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
import static android.net.thread.ThreadNetworkController.STATE_DISABLING;
import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
import static android.net.thread.ThreadNetworkController.THREAD_VERSION_1_3;
import static android.net.thread.ThreadNetworkException.ERROR_ABORTED;
+import static android.net.thread.ThreadNetworkException.ERROR_BUSY;
import static android.net.thread.ThreadNetworkException.ERROR_FAILED_PRECONDITION;
import static android.net.thread.ThreadNetworkException.ERROR_REJECTED_BY_PEER;
import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
@@ -72,6 +75,8 @@
import android.os.HandlerThread;
import android.os.OutcomeReceiver;
import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.SparseIntArray;
import androidx.annotation.NonNull;
@@ -82,6 +87,8 @@
import com.android.net.thread.flags.Flags;
import com.android.testutils.FunctionalUtils.ThrowingRunnable;
+import kotlin.Triple;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
@@ -96,6 +103,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@@ -134,9 +142,13 @@
put(VALID_CHANNEL, VALID_POWER);
}
};
+ private static final Duration EPHEMERAL_KEY_LIFETIME = Duration.ofSeconds(1);
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private final Context mContext = ApplicationProvider.getApplicationContext();
private ExecutorService mExecutor;
private ThreadNetworkController mController;
@@ -164,11 +176,13 @@
setEnabledAndWait(mController, true);
setConfigurationAndWait(mController, DEFAULT_CONFIG);
+ deactivateEphemeralKeyModeAndWait(mController);
}
@After
public void tearDown() throws Exception {
dropAllPermissions();
+ setEnabledAndWait(mController, true);
leaveAndWait(mController);
tearDownTestNetwork();
setConfigurationAndWait(mController, DEFAULT_CONFIG);
@@ -183,6 +197,7 @@
}
}
mConfigurationCallbacksToCleanUp.clear();
+ deactivateEphemeralKeyModeAndWait(mController);
}
@Test
@@ -819,6 +834,223 @@
listener.unregisterStateCallback();
}
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
+ public void getMaxEphemeralKeyLifetime_isLargerThanZero() {
+ assertThat(mController.getMaxEphemeralKeyLifetime()).isGreaterThan(Duration.ZERO);
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
+ public void activateEphemeralKeyMode_withPrivilegedPermission_succeeds() throws Exception {
+ joinRandomizedDatasetAndWait(mController);
+ CompletableFuture<Void> startFuture = new CompletableFuture<>();
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () ->
+ mController.activateEphemeralKeyMode(
+ EPHEMERAL_KEY_LIFETIME,
+ mExecutor,
+ newOutcomeReceiver(startFuture)));
+
+ startFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
+ public void activateEphemeralKeyMode_withoutPrivilegedPermission_throwsSecurityException()
+ throws Exception {
+ dropAllPermissions();
+
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mController.activateEphemeralKeyMode(
+ EPHEMERAL_KEY_LIFETIME, mExecutor, v -> {}));
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
+ public void activateEphemeralKeyMode_withZeroLifetime_throwsIllegalArgumentException()
+ throws Exception {
+ grantPermissions(THREAD_NETWORK_PRIVILEGED);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.activateEphemeralKeyMode(Duration.ZERO, mExecutor, v -> {}));
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
+ public void activateEphemeralKeyMode_withInvalidLargeLifetime_throwsIllegalArgumentException()
+ throws Exception {
+ grantPermissions(THREAD_NETWORK_PRIVILEGED);
+ Duration lifetime = mController.getMaxEphemeralKeyLifetime().plusMillis(1);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.activateEphemeralKeyMode(lifetime, Runnable::run, v -> {}));
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
+ public void activateEphemeralKeyMode_concurrentRequests_secondOneFailsWithBusyError()
+ throws Exception {
+ joinRandomizedDatasetAndWait(mController);
+ CompletableFuture<Void> future1 = new CompletableFuture<>();
+ CompletableFuture<Void> future2 = new CompletableFuture<>();
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> {
+ mController.activateEphemeralKeyMode(
+ EPHEMERAL_KEY_LIFETIME, mExecutor, newOutcomeReceiver(future1));
+ mController.activateEphemeralKeyMode(
+ EPHEMERAL_KEY_LIFETIME, mExecutor, newOutcomeReceiver(future2));
+ });
+
+ var thrown =
+ assertThrows(
+ ExecutionException.class,
+ () -> {
+ future2.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+ });
+ var threadException = (ThreadNetworkException) thrown.getCause();
+ assertThat(threadException.getErrorCode()).isEqualTo(ERROR_BUSY);
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
+ public void deactivateEphemeralKeyMode_withoutPrivilegedPermission_throwsSecurityException()
+ throws Exception {
+ dropAllPermissions();
+
+ assertThrows(
+ SecurityException.class,
+ () -> mController.deactivateEphemeralKeyMode(mExecutor, v -> {}));
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
+ public void subscribeEpskcState_permissionsGranted_returnsCurrentState() throws Exception {
+ CompletableFuture<Integer> stateFuture = new CompletableFuture<>();
+ CompletableFuture<String> ephemeralKeyFuture = new CompletableFuture<>();
+ CompletableFuture<Instant> expiryFuture = new CompletableFuture<>();
+ StateCallback callback =
+ new ThreadNetworkController.StateCallback() {
+ @Override
+ public void onDeviceRoleChanged(int r) {}
+
+ @Override
+ public void onEphemeralKeyStateChanged(
+ int state, String ephemeralKey, Instant expiry) {
+ stateFuture.complete(state);
+ ephemeralKeyFuture.complete(ephemeralKey);
+ expiryFuture.complete(expiry);
+ }
+ };
+
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.registerStateCallback(mExecutor, callback));
+
+ try {
+ assertThat(stateFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS))
+ .isEqualTo(EPHEMERAL_KEY_DISABLED);
+ assertThat(ephemeralKeyFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
+ assertThat(expiryFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
+ } finally {
+ runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
+ public void subscribeEpskcState_withoutThreadPriviledgedPermission_returnsNullEphemeralKey()
+ throws Exception {
+ CompletableFuture<Integer> stateFuture = new CompletableFuture<>();
+ CompletableFuture<String> ephemeralKeyFuture = new CompletableFuture<>();
+ CompletableFuture<Instant> expiryFuture = new CompletableFuture<>();
+ StateCallback callback =
+ new ThreadNetworkController.StateCallback() {
+ @Override
+ public void onDeviceRoleChanged(int r) {}
+
+ @Override
+ public void onEphemeralKeyStateChanged(
+ int state, String ephemeralKey, Instant expiry) {
+ stateFuture.complete(state);
+ ephemeralKeyFuture.complete(ephemeralKey);
+ expiryFuture.complete(expiry);
+ }
+ };
+ joinRandomizedDatasetAndWait(mController);
+ activateEphemeralKeyModeAndWait(mController);
+
+ runAsShell(
+ ACCESS_NETWORK_STATE, () -> mController.registerStateCallback(mExecutor, callback));
+
+ try {
+ assertThat(stateFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS))
+ .isEqualTo(EPHEMERAL_KEY_ENABLED);
+ assertThat(ephemeralKeyFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
+ assertThat(
+ expiryFuture
+ .get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)
+ .isAfter(Instant.now()))
+ .isTrue();
+ } finally {
+ runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
+ public void subscribeEpskcState_ephemralKeyStateChanged_returnsUpdatedState() throws Exception {
+ EphemeralKeyStateListener listener = new EphemeralKeyStateListener(mController);
+ joinRandomizedDatasetAndWait(mController);
+
+ try {
+ activateEphemeralKeyModeAndWait(mController);
+ deactivateEphemeralKeyModeAndWait(mController);
+
+ listener.expectThreadEphemeralKeyMode(EPHEMERAL_KEY_DISABLED);
+ listener.expectThreadEphemeralKeyMode(EPHEMERAL_KEY_ENABLED);
+ listener.expectThreadEphemeralKeyMode(EPHEMERAL_KEY_DISABLED);
+ } finally {
+ listener.unregisterStateCallback();
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_EPSKC_ENABLED})
+ public void subscribeEpskcState_epskcEnabled_returnsSameExpiry() throws Exception {
+ EphemeralKeyStateListener listener1 = new EphemeralKeyStateListener(mController);
+ Triple<Integer, String, Instant> epskc1;
+ try {
+ joinRandomizedDatasetAndWait(mController);
+ activateEphemeralKeyModeAndWait(mController);
+ epskc1 = listener1.expectThreadEphemeralKeyMode(EPHEMERAL_KEY_ENABLED);
+ } finally {
+ listener1.unregisterStateCallback();
+ }
+
+ EphemeralKeyStateListener listener2 = new EphemeralKeyStateListener(mController);
+ try {
+ Triple<Integer, String, Instant> epskc2 =
+ listener2.expectThreadEphemeralKeyMode(EPHEMERAL_KEY_ENABLED);
+
+ assertThat(epskc2.getSecond()).isEqualTo(epskc1.getSecond());
+ // allow time precision loss of a second since the value is passed via IPC
+ assertThat(epskc2.getThird()).isGreaterThan(epskc1.getThird().minusSeconds(1));
+ assertThat(epskc2.getThird()).isLessThan(epskc1.getThird().plusSeconds(1));
+ } finally {
+ listener2.unregisterStateCallback();
+ }
+ }
+
// TODO (b/322437869): add test case to verify when Thread is in DISABLING state, any commands
// (join/leave/scheduleMigration/setEnabled) fail with ERROR_BUSY. This is not currently tested
// because DISABLING has very short lifecycle, it's not possible to guarantee the command can be
@@ -919,15 +1151,9 @@
CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
ConfigurationListener listener = new ConfigurationListener(mController);
ThreadConfiguration config1 =
- new ThreadConfiguration.Builder()
- .setNat64Enabled(true)
- .setDhcpv6PdEnabled(true)
- .build();
+ new ThreadConfiguration.Builder().setNat64Enabled(true).build();
ThreadConfiguration config2 =
- new ThreadConfiguration.Builder()
- .setNat64Enabled(false)
- .setDhcpv6PdEnabled(true)
- .build();
+ new ThreadConfiguration.Builder().setNat64Enabled(false).build();
try {
runAsShell(
@@ -1274,6 +1500,71 @@
setFuture.get(SET_CONFIGURATION_TIMEOUT_MILLIS, MILLISECONDS);
}
+ private void deactivateEphemeralKeyModeAndWait(ThreadNetworkController controller)
+ throws Exception {
+ CompletableFuture<Void> clearFuture = new CompletableFuture<>();
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () ->
+ controller.deactivateEphemeralKeyMode(
+ mExecutor, newOutcomeReceiver(clearFuture)));
+ clearFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+ }
+
+ private void activateEphemeralKeyModeAndWait(ThreadNetworkController controller)
+ throws Exception {
+ CompletableFuture<Void> startFuture = new CompletableFuture<>();
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () ->
+ controller.activateEphemeralKeyMode(
+ EPHEMERAL_KEY_LIFETIME,
+ mExecutor,
+ newOutcomeReceiver(startFuture)));
+ startFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+ }
+
+ private class EphemeralKeyStateListener {
+ private ArrayTrackRecord<Triple<Integer, String, Instant>> mEphemeralKeyStates =
+ new ArrayTrackRecord<>();
+ private final ArrayTrackRecord<Triple<Integer, String, Instant>>.ReadHead mReadHead =
+ mEphemeralKeyStates.newReadHead();
+ ThreadNetworkController mController;
+ StateCallback mCallback =
+ new ThreadNetworkController.StateCallback() {
+ @Override
+ public void onDeviceRoleChanged(int r) {}
+
+ @Override
+ public void onEphemeralKeyStateChanged(
+ int state, String ephemeralKey, Instant expiry) {
+ mEphemeralKeyStates.add(new Triple<>(state, ephemeralKey, expiry));
+ }
+ };
+
+ EphemeralKeyStateListener(ThreadNetworkController controller) {
+ this.mController = controller;
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ THREAD_NETWORK_PRIVILEGED,
+ () -> controller.registerStateCallback(mExecutor, mCallback));
+ }
+
+ // Expect that EphemeralKey has the expected state, and return a Triple of <state,
+ // passcode, expiry>.
+ public Triple<Integer, String, Instant> expectThreadEphemeralKeyMode(int state) {
+ Triple<Integer, String, Instant> epskc =
+ mReadHead.poll(
+ ENABLED_TIMEOUT_MILLIS, e -> Objects.equals(e.getFirst(), state));
+ assertThat(epskc).isNotNull();
+ return epskc;
+ }
+
+ public void unregisterStateCallback() {
+ runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(mCallback));
+ }
+ }
+
private CompletableFuture joinRandomizedDataset(
ThreadNetworkController controller, String networkName) throws Exception {
ActiveOperationalDataset activeDataset = newRandomizedDataset(networkName, controller);
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 103282a..cf7a4f7 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -29,6 +29,7 @@
import static android.net.thread.utils.IntegrationTestUtils.newPacketReader;
import static android.net.thread.utils.IntegrationTestUtils.pollForPacket;
import static android.net.thread.utils.IntegrationTestUtils.sendUdpMessage;
+import static android.net.thread.utils.IntegrationTestUtils.stopOtDaemon;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static android.system.OsConstants.ICMP_ECHO;
@@ -48,6 +49,8 @@
import android.content.Context;
import android.net.IpPrefix;
import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
import android.net.thread.utils.FullThreadDevice;
import android.net.thread.utils.InfraNetworkDevice;
import android.net.thread.utils.IntegrationTestUtils;
@@ -65,7 +68,7 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.testutils.TapPacketReader;
+import com.android.testutils.PollPacketReader;
import com.android.testutils.TestNetworkTracker;
import org.junit.After;
@@ -99,6 +102,11 @@
private static final Inet4Address IPV4_SERVER_ADDR =
(Inet4Address) parseNumericAddress("8.8.8.8");
private static final String NAT64_CIDR = "192.168.255.0/24";
+ private static final IpPrefix DHCP6_PD_PREFIX = new IpPrefix("2001:db8::/64");
+ private static final IpPrefix AIL_NAT64_PREFIX = new IpPrefix("2001:db8:1234::/96");
+ private static final Inet6Address AIL_NAT64_SYNTHESIZED_SERVER_ADDR =
+ (Inet6Address) parseNumericAddress("2001:db8:1234::8.8.8.8");
+ private static final Duration UPDATE_NAT64_PREFIX_TIMEOUT = Duration.ofSeconds(10);
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
@@ -110,7 +118,7 @@
private Handler mHandler;
private TestNetworkTracker mInfraNetworkTracker;
private List<FullThreadDevice> mFtds;
- private TapPacketReader mInfraNetworkReader;
+ private PollPacketReader mInfraNetworkReader;
private InfraNetworkDevice mInfraDevice;
@Before
@@ -266,6 +274,28 @@
}
@Test
+ public void unicastRouting_otDaemonRestarts_borderRoutingWorks() throws Exception {
+ /*
+ * <pre>
+ * Topology:
+ * infra network Thread
+ * infra device -------------------- Border Router -------------- Full Thread device
+ * (Cuttlefish)
+ * </pre>
+ */
+
+ FullThreadDevice ftd = mFtds.get(0);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
+
+ stopOtDaemon();
+ ftd.waitForStateAnyOf(List.of("leader", "router", "child"), Duration.ofSeconds(40));
+
+ startInfraDeviceAndWaitForOnLinkAddr();
+ mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
+ assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ }
+
+ @Test
@RequiresIpv6MulticastRouting
public void multicastRouting_ftdSubscribedMulticastAddress_infraLinkJoinsMulticastGroup()
throws Exception {
@@ -623,13 +653,50 @@
// TODO: enable NAT64 via ThreadNetworkController API instead of ot-ctl
mOtCtl.setNat64Cidr(NAT64_CIDR);
mOtCtl.setNat64Enabled(true);
- waitFor(() -> mOtCtl.hasNat64PrefixInNetdata(), Duration.ofSeconds(10));
+ waitFor(() -> mOtCtl.hasNat64PrefixInNetdata(), UPDATE_NAT64_PREFIX_TIMEOUT);
ftd.ping(IPV4_SERVER_ADDR);
assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMP_ECHO, null, IPV4_SERVER_ADDR));
}
+ @Test
+ public void nat64_withAilNat64Prefix_threadDevicePingIpv4InfraDevice_outboundPacketIsForwarded()
+ throws Exception {
+ tearDownInfraNetwork();
+ LinkProperties lp = new LinkProperties();
+ // NAT64 feature requires the infra network to have an IPv4 default route.
+ lp.addRoute(
+ new RouteInfo(
+ new IpPrefix("0.0.0.0/0") /* destination */,
+ null /* gateway */,
+ null /* iface */,
+ RouteInfo.RTN_UNICAST,
+ 1500 /* mtu */));
+ lp.addRoute(
+ new RouteInfo(
+ new IpPrefix("::/0") /* destination */,
+ null /* gateway */,
+ null /* iface */,
+ RouteInfo.RTN_UNICAST,
+ 1500 /* mtu */));
+ lp.setNat64Prefix(AIL_NAT64_PREFIX);
+ mInfraNetworkTracker = IntegrationTestUtils.setUpInfraNetwork(mContext, mController, lp);
+ mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
+ FullThreadDevice ftd = mFtds.get(0);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
+ // TODO: enable NAT64 via ThreadNetworkController API instead of ot-ctl
+ mOtCtl.setNat64Enabled(true);
+ mOtCtl.addPrefixInNetworkData(DHCP6_PD_PREFIX, "paros", "med");
+ waitFor(() -> mOtCtl.hasNat64PrefixInNetdata(), UPDATE_NAT64_PREFIX_TIMEOUT);
+
+ ftd.ping(IPV4_SERVER_ADDR);
+
+ assertNotNull(
+ pollForIcmpPacketOnInfraNetwork(
+ ICMPV6_ECHO_REQUEST_TYPE, null, AIL_NAT64_SYNTHESIZED_SERVER_ADDR));
+ }
+
private void setUpInfraNetwork() throws Exception {
mInfraNetworkTracker = IntegrationTestUtils.setUpInfraNetwork(mContext, mController);
}
diff --git a/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java b/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
index 72a278c..cb0c8ee 100644
--- a/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/InfraNetworkDevice.java
@@ -28,7 +28,7 @@
import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.structs.LlaOption;
import com.android.net.module.util.structs.PrefixInformationOption;
-import com.android.testutils.TapPacketReader;
+import com.android.testutils.PollPacketReader;
import java.io.IOException;
import java.net.Inet6Address;
@@ -49,18 +49,18 @@
// The MAC address of this device.
public final MacAddress macAddr;
// The packet reader of the TUN interface of the test network.
- public final TapPacketReader packetReader;
+ public final PollPacketReader packetReader;
// The IPv6 address generated by SLAAC for the device.
public Inet6Address ipv6Addr;
/**
* Constructs an InfraNetworkDevice with the given {@link MAC address} and {@link
- * TapPacketReader}.
+ * PollPacketReader}.
*
* @param macAddr the MAC address of the device
* @param packetReader the packet reader of the TUN interface of the test network.
*/
- public InfraNetworkDevice(MacAddress macAddr, TapPacketReader packetReader) {
+ public InfraNetworkDevice(MacAddress macAddr, PollPacketReader packetReader) {
this.macAddr = macAddr;
this.packetReader = packetReader;
}
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
index fa9855e..116fb72 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.kt
@@ -47,7 +47,7 @@
import com.android.net.module.util.structs.Ipv6Header
import com.android.net.module.util.structs.PrefixInformationOption
import com.android.net.module.util.structs.RaHeader
-import com.android.testutils.TapPacketReader
+import com.android.testutils.PollPacketReader
import com.android.testutils.TestNetworkTracker
import com.android.testutils.initTestNetwork
import com.android.testutils.runAsShell
@@ -136,18 +136,18 @@
}
/**
- * Creates a [TapPacketReader] given the [TestNetworkInterface] and [Handler].
+ * Creates a [PollPacketReader] given the [TestNetworkInterface] and [Handler].
*
* @param testNetworkInterface the TUN interface of the test network
* @param handler the handler to process the packets
- * @return the [TapPacketReader]
+ * @return the [PollPacketReader]
*/
@JvmStatic
fun newPacketReader(
testNetworkInterface: TestNetworkInterface, handler: Handler
- ): TapPacketReader {
+ ): PollPacketReader {
val fd = testNetworkInterface.fileDescriptor.fileDescriptor
- val reader = TapPacketReader(handler, fd, testNetworkInterface.mtu)
+ val reader = PollPacketReader(handler, fd, testNetworkInterface.mtu)
handler.post { reader.start() }
handler.waitForIdle(timeoutMs = 5000)
return reader
@@ -191,7 +191,7 @@
}
/**
- * Polls for a packet from a given [TapPacketReader] that satisfies the `filter`.
+ * Polls for a packet from a given [PollPacketReader] that satisfies the `filter`.
*
* @param packetReader a TUN packet reader
* @param filter the filter to be applied on the packet
@@ -199,7 +199,7 @@
* than 3000ms to read the next packet, the method will return null
*/
@JvmStatic
- fun pollForPacket(packetReader: TapPacketReader, filter: Predicate<ByteArray>): ByteArray? {
+ fun pollForPacket(packetReader: PollPacketReader, filter: Predicate<ByteArray>): ByteArray? {
var packet: ByteArray?
while ((packetReader.poll(3000 /* timeoutMs */, filter).also { packet = it }) != null) {
return packet
@@ -551,25 +551,8 @@
)
}
- @JvmStatic
- @JvmOverloads
- fun startInfraDeviceAndWaitForOnLinkAddr(
- tapPacketReader: TapPacketReader,
- macAddress: MacAddress = MacAddress.fromString("1:2:3:4:5:6")
- ): InfraNetworkDevice {
- val infraDevice = InfraNetworkDevice(macAddress, tapPacketReader)
- infraDevice.runSlaac(Duration.ofSeconds(60))
- requireNotNull(infraDevice.ipv6Addr)
- return infraDevice
- }
-
- @JvmStatic
- @Throws(java.lang.Exception::class)
- fun setUpInfraNetwork(
- context: Context, controller: ThreadNetworkControllerWrapper
- ): TestNetworkTracker {
+ private fun defaultLinkProperties(): LinkProperties {
val lp = LinkProperties()
-
// TODO: use a fake DNS server
lp.setDnsServers(listOf(parseNumericAddress("8.8.8.8")))
// NAT64 feature requires the infra network to have an IPv4 default route.
@@ -581,6 +564,29 @@
RouteInfo.RTN_UNICAST, 1500 /* mtu */
)
)
+ return lp
+ }
+
+ @JvmStatic
+ @JvmOverloads
+ fun startInfraDeviceAndWaitForOnLinkAddr(
+ pollPacketReader: PollPacketReader,
+ macAddress: MacAddress = MacAddress.fromString("1:2:3:4:5:6")
+ ): InfraNetworkDevice {
+ val infraDevice = InfraNetworkDevice(macAddress, pollPacketReader)
+ infraDevice.runSlaac(Duration.ofSeconds(60))
+ requireNotNull(infraDevice.ipv6Addr)
+ return infraDevice
+ }
+
+ @JvmStatic
+ @JvmOverloads
+ @Throws(java.lang.Exception::class)
+ fun setUpInfraNetwork(
+ context: Context,
+ controller: ThreadNetworkControllerWrapper,
+ lp: LinkProperties = defaultLinkProperties()
+ ): TestNetworkTracker {
val infraNetworkTracker: TestNetworkTracker =
runAsShell(
MANAGE_TEST_NETWORKS,
@@ -595,4 +601,12 @@
fun tearDownInfraNetwork(testNetworkTracker: TestNetworkTracker) {
runAsShell(MANAGE_TEST_NETWORKS) { testNetworkTracker.teardown() }
}
+
+ /**
+ * Stop the ot-daemon by shell command.
+ */
+ @JvmStatic
+ fun stopOtDaemon() {
+ runShellCommandOrThrow("stop ot-daemon")
+ }
}
diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
index 15a3f5c..046d9bf 100644
--- a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
+++ b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
@@ -128,6 +128,12 @@
return false;
}
+ /** Adds a prefix in the Network Data. */
+ public void addPrefixInNetworkData(IpPrefix ipPrefix, String flags, String preference) {
+ executeCommand("prefix add " + ipPrefix + " " + flags + " " + preference);
+ executeCommand("netdata register");
+ }
+
public String executeCommand(String cmd) {
return SystemUtil.runShellCommand(OT_CTL + " " + cmd);
}
diff --git a/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java b/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
index 0423578..e3c83f1 100644
--- a/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
+++ b/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
@@ -17,6 +17,8 @@
package android.net.thread;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_CHILD;
+import static android.net.thread.ThreadNetworkException.ERROR_FAILED_PRECONDITION;
+import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
import static android.net.thread.ThreadNetworkException.ERROR_UNAVAILABLE;
import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_CHANNEL;
import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_FEATURE;
@@ -26,6 +28,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
@@ -134,6 +137,24 @@
return (IOperationalDatasetCallback) invocation.getArguments()[0];
}
+ private static IOperationReceiver getActivateEphemeralKeyModeReceiver(
+ InvocationOnMock invocation) {
+ return (IOperationReceiver) invocation.getArguments()[1];
+ }
+
+ private static IOperationReceiver getDeactivateEphemeralKeyModeReceiver(
+ InvocationOnMock invocation) {
+ return (IOperationReceiver) invocation.getArguments()[0];
+ }
+
+ private static IOperationReceiver getSetConfigurationReceiver(InvocationOnMock invocation) {
+ return (IOperationReceiver) invocation.getArguments()[1];
+ }
+
+ private static IConfigurationReceiver getConfigurationReceiver(InvocationOnMock invocation) {
+ return (IConfigurationReceiver) invocation.getArguments()[0];
+ }
+
@Test
public void registerStateCallback_callbackIsInvokedWithCallingAppIdentity() throws Exception {
setBinderUid(SYSTEM_UID);
@@ -440,4 +461,152 @@
assertThat(callbackUid.get()).isNotEqualTo(SYSTEM_UID);
assertThat(callbackUid.get()).isEqualTo(Process.myUid());
}
+
+ @Test
+ public void activateEphemeralKeyMode_callbackIsInvokedWithCallingAppIdentity()
+ throws Exception {
+ setBinderUid(SYSTEM_UID);
+ AtomicInteger successCallbackUid = new AtomicInteger(0);
+ AtomicInteger errorCallbackUid = new AtomicInteger(0);
+ Duration lifetime = Duration.ofSeconds(100);
+ doAnswer(
+ invoke -> {
+ getActivateEphemeralKeyModeReceiver(invoke).onSuccess();
+ return null;
+ })
+ .when(mMockService)
+ .activateEphemeralKeyMode(anyLong(), any(IOperationReceiver.class));
+ mController.activateEphemeralKeyMode(
+ lifetime, Runnable::run, v -> successCallbackUid.set(Binder.getCallingUid()));
+ doAnswer(
+ invoke -> {
+ getActivateEphemeralKeyModeReceiver(invoke)
+ .onError(ERROR_FAILED_PRECONDITION, "");
+ return null;
+ })
+ .when(mMockService)
+ .activateEphemeralKeyMode(anyLong(), any(IOperationReceiver.class));
+ mController.activateEphemeralKeyMode(
+ lifetime,
+ Runnable::run,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Void unused) {}
+
+ @Override
+ public void onError(ThreadNetworkException e) {
+ errorCallbackUid.set(Binder.getCallingUid());
+ }
+ });
+
+ assertThat(successCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(successCallbackUid.get()).isEqualTo(Process.myUid());
+ assertThat(errorCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(errorCallbackUid.get()).isEqualTo(Process.myUid());
+ }
+
+ @Test
+ public void deactivateEphemeralKeyMode_callbackIsInvokedWithCallingAppIdentity()
+ throws Exception {
+ setBinderUid(SYSTEM_UID);
+ AtomicInteger successCallbackUid = new AtomicInteger(0);
+ AtomicInteger errorCallbackUid = new AtomicInteger(0);
+ doAnswer(
+ invoke -> {
+ getDeactivateEphemeralKeyModeReceiver(invoke).onSuccess();
+ return null;
+ })
+ .when(mMockService)
+ .deactivateEphemeralKeyMode(any(IOperationReceiver.class));
+ mController.deactivateEphemeralKeyMode(
+ Runnable::run, v -> successCallbackUid.set(Binder.getCallingUid()));
+ doAnswer(
+ invoke -> {
+ getDeactivateEphemeralKeyModeReceiver(invoke)
+ .onError(ERROR_INTERNAL_ERROR, "");
+ return null;
+ })
+ .when(mMockService)
+ .deactivateEphemeralKeyMode(any(IOperationReceiver.class));
+ mController.deactivateEphemeralKeyMode(
+ Runnable::run,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Void unused) {}
+
+ @Override
+ public void onError(ThreadNetworkException e) {
+ errorCallbackUid.set(Binder.getCallingUid());
+ }
+ });
+
+ assertThat(successCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(successCallbackUid.get()).isEqualTo(Process.myUid());
+ assertThat(errorCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(errorCallbackUid.get()).isEqualTo(Process.myUid());
+ }
+
+ @Test
+ public void setConfiguration_callbackIsInvokedWithCallingAppIdentity() throws Exception {
+ setBinderUid(SYSTEM_UID);
+ AtomicInteger successCallbackUid = new AtomicInteger(0);
+ AtomicInteger errorCallbackUid = new AtomicInteger(0);
+ doAnswer(
+ invoke -> {
+ getSetConfigurationReceiver(invoke).onSuccess();
+ return null;
+ })
+ .when(mMockService)
+ .setConfiguration(any(ThreadConfiguration.class), any(IOperationReceiver.class));
+ mController.setConfiguration(
+ new ThreadConfiguration.Builder().build(),
+ Runnable::run,
+ v -> successCallbackUid.set(Binder.getCallingUid()));
+ doAnswer(
+ invoke -> {
+ getSetConfigurationReceiver(invoke).onError(ERROR_INTERNAL_ERROR, "");
+ return null;
+ })
+ .when(mMockService)
+ .setConfiguration(any(ThreadConfiguration.class), any(IOperationReceiver.class));
+ mController.setConfiguration(
+ new ThreadConfiguration.Builder().build(),
+ Runnable::run,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Void unused) {}
+
+ @Override
+ public void onError(ThreadNetworkException e) {
+ errorCallbackUid.set(Binder.getCallingUid());
+ }
+ });
+
+ assertThat(successCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(successCallbackUid.get()).isEqualTo(Process.myUid());
+ assertThat(errorCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(errorCallbackUid.get()).isEqualTo(Process.myUid());
+ }
+
+ @Test
+ public void registerConfigurationCallback_callbackIsInvokedWithCallingAppIdentity()
+ throws Exception {
+ setBinderUid(SYSTEM_UID);
+ AtomicInteger callbackUid = new AtomicInteger(0);
+ doAnswer(
+ invoke -> {
+ getConfigurationReceiver(invoke)
+ .onConfigurationChanged(
+ new ThreadConfiguration.Builder().build());
+ return null;
+ })
+ .when(mMockService)
+ .registerConfigurationCallback(any(IConfigurationReceiver.class));
+
+ mController.registerConfigurationCallback(
+ Runnable::run, v -> callbackUid.set(Binder.getCallingUid()));
+
+ assertThat(callbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(callbackUid.get()).isEqualTo(Process.myUid());
+ }
}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index d8cdbc4..7ac404f 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -741,10 +741,7 @@
.setDhcpv6PdEnabled(false)
.build();
ThreadConfiguration config2 =
- new ThreadConfiguration.Builder()
- .setNat64Enabled(true)
- .setDhcpv6PdEnabled(true)
- .build();
+ new ThreadConfiguration.Builder().setNat64Enabled(true).build();
ThreadConfiguration config3 =
new ThreadConfiguration.Builder(config2).build(); // Same as config2
@@ -838,4 +835,26 @@
verify(mockReceiver, times(1)).onError(eq(ERROR_INTERNAL_ERROR), anyString());
}
+
+ @Test
+ public void activateEphemeralKeyMode_succeed() throws Exception {
+ mService.initialize();
+ final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
+
+ mService.activateEphemeralKeyMode(1_000L, mockReceiver);
+ mTestLooper.dispatchAll();
+
+ verify(mockReceiver, times(1)).onSuccess();
+ }
+
+ @Test
+ public void deactivateEphemeralKeyMode_succeed() throws Exception {
+ mService.initialize();
+ final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
+
+ mService.deactivateEphemeralKeyMode(mockReceiver);
+ mTestLooper.dispatchAll();
+
+ verify(mockReceiver, times(1)).onSuccess();
+ }
}