[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 6922cab3fd -s ours
am skip reason: contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/28040389
Change-Id: I80431755eef25a5c5ff33f1128e23bcd54b85cb8
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/OWNERS_core_networking_xts b/OWNERS_core_networking_xts
index b24e3ac..9e4e4a1 100644
--- a/OWNERS_core_networking_xts
+++ b/OWNERS_core_networking_xts
@@ -1,11 +1,12 @@
lorenzo@google.com
satk@google.com #{LAST_RESORT_SUGGESTION}
-# For cherry-picks of CLs that are already merged in aosp/master, or flaky test fixes.
+# For cherry-picks of CLs that are already merged in aosp/master, flaky test
+# fixes, or no-op refactors.
jchalard@google.com #{LAST_RESORT_SUGGESTION}
-# In addition to cherry-picks and flaky test fixes, also for APF firmware tests
-# (to verify correct behaviour of the wifi APF interpreter)
+# In addition to cherry-picks, flaky test fixes and no-op refactors, also for
+# APF firmware tests (to verify correct behaviour of the wifi APF interpreter)
maze@google.com #{LAST_RESORT_SUGGESTION}
-# In addition to cherry-picks and flaky test fixes, also for incremental changes on NsdManager tests
-# to increase coverage for existing behavior, and testing of bug fixes in NsdManager
+# In addition to cherry-picks, flaky test fixes and no-op refactors, also for
+# NsdManager tests
reminv@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 375397b..4cf93a8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -77,18 +77,6 @@
"name": "libnetworkstats_test"
},
{
- "name": "NetHttpCoverageTests",
- "options": [
- {
- "exclude-annotation": "com.android.testutils.SkipPresubmit"
- },
- {
- // These sometimes take longer than 1 min which is the presubmit timeout
- "exclude-annotation": "androidx.test.filters.LargeTest"
- }
- ]
- },
- {
"name": "CtsTetheringTestLatestSdk",
"options": [
{
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 19bcff9..e84573b 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -126,7 +126,7 @@
// Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK).
cc_library {
name: "libcom_android_networkstack_tethering_util_jni",
- sdk_version: "30",
+ sdk_version: "current",
apex_available: [
"com.android.tethering",
],
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 53bb947..8ed5ac0 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -23,23 +23,26 @@
// different value depending on the branch.
java_defaults {
name: "ConnectivityNextEnableDefaults",
- enabled: false,
+ enabled: true,
}
+
java_defaults {
name: "NetworkStackApiShimSettingsForCurrentBranch",
// API shims to include in the networking modules built from the branch. Branches that disable
// the "next" targets must use stable shims (latest stable API level) instead of current shims
// (X_current API level).
- static_libs: ["NetworkStackApiStableShims"],
+ static_libs: ["NetworkStackApiCurrentShims"],
}
+
apex_defaults {
name: "ConnectivityApexDefaults",
// Tethering app to include in the AOSP apex. Branches that disable the "next" targets may use
// a stable tethering app instead, but will generally override the AOSP apex to use updatable
// package names and keys, so that apex will be unused anyway.
- apps: ["Tethering"], // Replace to "Tethering" if ConnectivityNextEnableDefaults is false.
+ apps: ["TetheringNext"], // Replace to "Tethering" if ConnectivityNextEnableDefaults is false.
}
-enable_tethering_next_apex = false
+
+enable_tethering_next_apex = true
// This is a placeholder comment to avoid merge conflicts
// as the above target may have different "enabled" values
// depending on the branch
@@ -109,7 +112,7 @@
],
prebuilts: [
"current_sdkinfo",
- "netbpfload.mainline.rc",
+ "netbpfload.33rc",
"netbpfload.35rc",
"ot-daemon.init.34rc",
],
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index a287b42..cccafd5 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -47,6 +47,7 @@
field public static final int TETHERING_INVALID = -1; // 0xffffffff
field public static final int TETHERING_NCM = 4; // 0x4
field public static final int TETHERING_USB = 1; // 0x1
+ field @FlaggedApi("com.android.net.flags.tethering_request_virtual") public static final int TETHERING_VIRTUAL = 7; // 0x7
field public static final int TETHERING_WIFI = 0; // 0x0
field public static final int TETHERING_WIFI_P2P = 3; // 0x3
field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 7b769d4..2963f87 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -68,6 +68,8 @@
public static class Flags {
static final String TETHERING_REQUEST_WITH_SOFT_AP_CONFIG =
"com.android.net.flags.tethering_request_with_soft_ap_config";
+ static final String TETHERING_REQUEST_VIRTUAL =
+ "com.android.net.flags.tethering_request_virtual";
}
private static final String TAG = TetheringManager.class.getSimpleName();
@@ -195,10 +197,18 @@
public static final int TETHERING_WIGIG = 6;
/**
+ * VIRTUAL tethering type.
+ * @hide
+ */
+ @FlaggedApi(Flags.TETHERING_REQUEST_VIRTUAL)
+ @SystemApi
+ public static final int TETHERING_VIRTUAL = 7;
+
+ /**
* The int value of last tethering type.
* @hide
*/
- public static final int MAX_TETHERING_TYPE = TETHERING_WIGIG;
+ public static final int MAX_TETHERING_TYPE = TETHERING_VIRTUAL;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 9e0c970..fe5a0c6 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -34,7 +34,6 @@
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
import static com.android.networkstack.tethering.TetheringConfiguration.USE_SYNC_SM;
-import static com.android.networkstack.tethering.UpstreamNetworkState.isVcnInterface;
import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix;
import static com.android.networkstack.tethering.util.TetheringMessageBase.BASE_IPSERVER;
@@ -75,19 +74,15 @@
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.SyncStateMachine.StateInfo;
import com.android.net.module.util.ip.InterfaceController;
-import com.android.net.module.util.ip.IpNeighborMonitor;
-import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent;
import com.android.networkstack.tethering.BpfCoordinator;
-import com.android.networkstack.tethering.BpfCoordinator.ClientInfo;
-import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;
import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
import com.android.networkstack.tethering.util.StateMachineShim;
-import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -189,12 +184,6 @@
return new DadProxy(handler, ifParams);
}
- /** Create an IpNeighborMonitor to be used by this IpServer */
- public IpNeighborMonitor getIpNeighborMonitor(Handler handler, SharedLog log,
- IpNeighborMonitor.NeighborEventConsumer consumer) {
- return new IpNeighborMonitor(handler, log, consumer);
- }
-
/** Create a RouterAdvertisementDaemon instance to be used by IpServer.*/
public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) {
return new RouterAdvertisementDaemon(ifParams);
@@ -234,13 +223,11 @@
public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IPSERVER + 9;
// new IPv6 tethering parameters need to be processed
public static final int CMD_IPV6_TETHER_UPDATE = BASE_IPSERVER + 10;
- // new neighbor cache entry on our interface
- public static final int CMD_NEIGHBOR_EVENT = BASE_IPSERVER + 11;
// request from DHCP server that it wants to have a new prefix
- public static final int CMD_NEW_PREFIX_REQUEST = BASE_IPSERVER + 12;
+ public static final int CMD_NEW_PREFIX_REQUEST = BASE_IPSERVER + 11;
// request from PrivateAddressCoordinator to restart tethering.
- public static final int CMD_NOTIFY_PREFIX_CONFLICT = BASE_IPSERVER + 13;
- public static final int CMD_SERVICE_FAILED_TO_START = BASE_IPSERVER + 14;
+ public static final int CMD_NOTIFY_PREFIX_CONFLICT = BASE_IPSERVER + 12;
+ public static final int CMD_SERVICE_FAILED_TO_START = BASE_IPSERVER + 13;
private final State mInitialState;
private final State mLocalHotspotState;
@@ -301,18 +288,9 @@
private List<TetheredClient> mDhcpLeases = Collections.emptyList();
private int mLastIPv6UpstreamIfindex = 0;
- private boolean mUpstreamSupportsBpf = false;
@NonNull
private Set<IpPrefix> mLastIPv6UpstreamPrefixes = Collections.emptySet();
- private class MyNeighborEventConsumer implements IpNeighborMonitor.NeighborEventConsumer {
- public void accept(NeighborEvent e) {
- sendMessage(CMD_NEIGHBOR_EVENT, e);
- }
- }
-
- private final IpNeighborMonitor mIpNeighborMonitor;
-
private LinkAddress mIpv4Address;
private final TetheringMetrics mTetheringMetrics;
@@ -346,15 +324,6 @@
mLastError = TETHER_ERROR_NO_ERROR;
mServingMode = STATE_AVAILABLE;
- mIpNeighborMonitor = mDeps.getIpNeighborMonitor(getHandler(), mLog,
- new MyNeighborEventConsumer());
-
- // IP neighbor monitor monitors the neighbor events for adding/removing IPv6 downstream rule
- // per client. If BPF offload is not supported, don't start listening for neighbor events.
- if (mBpfCoordinator.isUsingBpfOffload() && !mIpNeighborMonitor.start()) {
- mLog.e("Failed to create IpNeighborMonitor on " + mIfaceName);
- }
-
mInitialState = new InitialState();
mLocalHotspotState = new LocalHotspotState();
mTetheredState = new TetheredState();
@@ -410,6 +379,22 @@
return mIpv4Address;
}
+ /** The IPv6 upstream interface index */
+ public int getIpv6UpstreamIfindex() {
+ return mLastIPv6UpstreamIfindex;
+ }
+
+ /** The IPv6 upstream interface prefixes */
+ @NonNull
+ public Set<IpPrefix> getIpv6UpstreamPrefixes() {
+ return Collections.unmodifiableSet(mLastIPv6UpstreamPrefixes);
+ }
+
+ /** The interface parameters which IpServer is using */
+ public InterfaceParams getInterfaceParams() {
+ return mInterfaceParams;
+ }
+
/**
* Get the latest list of DHCP leases that was reported. Must be called on the IpServer looper
* thread.
@@ -813,14 +798,15 @@
setRaParams(params);
// Not support BPF on virtual upstream interface
- final boolean upstreamSupportsBpf = upstreamIface != null && !isVcnInterface(upstreamIface);
final Set<IpPrefix> upstreamPrefixes = params != null ? params.prefixes : Set.of();
- updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamPrefixes,
- upstreamIfIndex, upstreamPrefixes, upstreamSupportsBpf);
+ // mBpfCoordinator#updateIpv6UpstreamInterface must be called before updating
+ // mLastIPv6UpstreamIfindex and mLastIPv6UpstreamPrefixes because BpfCoordinator will call
+ // IpServer#getIpv6UpstreamIfindex and IpServer#getIpv6UpstreamPrefixes to retrieve current
+ // upstream interface index and prefixes when handling upstream changes.
+ mBpfCoordinator.updateIpv6UpstreamInterface(this, upstreamIfIndex, upstreamPrefixes);
mLastIPv6LinkProperties = v6only;
mLastIPv6UpstreamIfindex = upstreamIfIndex;
mLastIPv6UpstreamPrefixes = upstreamPrefixes;
- mUpstreamSupportsBpf = upstreamSupportsBpf;
if (mDadProxy != null) {
mDadProxy.setUpstreamIface(upstreamIfaceParams);
}
@@ -964,77 +950,6 @@
}
}
- private int getInterfaceIndexForRule(int ifindex, boolean supportsBpf) {
- return supportsBpf ? ifindex : NO_UPSTREAM;
- }
-
- // Handles updates to IPv6 forwarding rules if the upstream or its prefixes change.
- private void updateIpv6ForwardingRules(int prevUpstreamIfindex,
- @NonNull Set<IpPrefix> prevUpstreamPrefixes, int upstreamIfindex,
- @NonNull Set<IpPrefix> upstreamPrefixes, boolean upstreamSupportsBpf) {
- // If the upstream interface has changed, remove all rules and re-add them with the new
- // upstream interface. If upstream is a virtual network, treated as no upstream.
- if (prevUpstreamIfindex != upstreamIfindex
- || !prevUpstreamPrefixes.equals(upstreamPrefixes)) {
- mBpfCoordinator.updateAllIpv6Rules(this, this.mInterfaceParams,
- getInterfaceIndexForRule(upstreamIfindex, upstreamSupportsBpf),
- upstreamPrefixes);
- }
- }
-
- // Handles updates to IPv6 downstream rules if a neighbor event is received.
- private void addOrRemoveIpv6Downstream(NeighborEvent e) {
- // mInterfaceParams must be non-null or the event would not have arrived.
- if (e == null) return;
- if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress()
- || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
- return;
- }
-
- // When deleting rules, we still need to pass a non-null MAC, even though it's ignored.
- // Do this here instead of in the Ipv6DownstreamRule constructor to ensure that we
- // never add rules with a null MAC, only delete them.
- MacAddress dstMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
- Ipv6DownstreamRule rule = new Ipv6DownstreamRule(
- getInterfaceIndexForRule(mLastIPv6UpstreamIfindex, mUpstreamSupportsBpf),
- mInterfaceParams.index, (Inet6Address) e.ip, mInterfaceParams.macAddr, dstMac);
- if (e.isValid()) {
- mBpfCoordinator.addIpv6DownstreamRule(this, rule);
- } else {
- mBpfCoordinator.removeIpv6DownstreamRule(this, rule);
- }
- }
-
- // TODO: consider moving into BpfCoordinator.
- private void updateClientInfoIpv4(NeighborEvent e) {
- if (e == null) return;
- if (!(e.ip instanceof Inet4Address) || e.ip.isMulticastAddress()
- || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
- return;
- }
-
- // When deleting clients, IpServer still need to pass a non-null MAC, even though it's
- // ignored. Do this here instead of in the ClientInfo constructor to ensure that
- // IpServer never add clients with a null MAC, only delete them.
- final MacAddress clientMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
- final ClientInfo clientInfo = new ClientInfo(mInterfaceParams.index,
- mInterfaceParams.macAddr, (Inet4Address) e.ip, clientMac);
- if (e.isValid()) {
- mBpfCoordinator.tetherOffloadClientAdd(this, clientInfo);
- } else {
- mBpfCoordinator.tetherOffloadClientRemove(this, clientInfo);
- }
- }
-
- private void handleNeighborEvent(NeighborEvent e) {
- if (mInterfaceParams != null
- && mInterfaceParams.index == e.ifindex
- && mInterfaceParams.hasMacAddress) {
- addOrRemoveIpv6Downstream(e);
- updateClientInfoIpv4(e);
- }
- }
-
private byte getHopLimit(String upstreamIface, int adjustTTL) {
try {
int upstreamHopLimit = Integer.parseUnsignedInt(
@@ -1069,7 +984,6 @@
switch (what) {
// Suppress some CMD_* to avoid log flooding.
case CMD_IPV6_TETHER_UPDATE:
- case CMD_NEIGHBOR_EVENT:
break;
default:
mLog.log(state.getName() + " got "
@@ -1141,14 +1055,6 @@
}
}
- private void startConntrackMonitoring() {
- mBpfCoordinator.startMonitoring(this);
- }
-
- private void stopConntrackMonitoring() {
- mBpfCoordinator.stopMonitoring(this);
- }
-
abstract class BaseServingState extends State {
private final int mDesiredInterfaceState;
@@ -1158,7 +1064,7 @@
@Override
public void enter() {
- startConntrackMonitoring();
+ mBpfCoordinator.addIpServer(IpServer.this);
startServingInterface();
@@ -1226,7 +1132,7 @@
}
stopIPv4();
- stopConntrackMonitoring();
+ mBpfCoordinator.removeIpServer(IpServer.this);
resetLinkProperties();
@@ -1397,8 +1303,8 @@
for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname);
mUpstreamIfaceSet = null;
- mBpfCoordinator.updateAllIpv6Rules(
- IpServer.this, IpServer.this.mInterfaceParams, NO_UPSTREAM, Set.of());
+ mBpfCoordinator.updateIpv6UpstreamInterface(IpServer.this, NO_UPSTREAM,
+ Collections.emptySet());
}
private void cleanupUpstreamInterface(String upstreamIface) {
@@ -1473,9 +1379,6 @@
}
}
break;
- case CMD_NEIGHBOR_EVENT:
- handleNeighborEvent((NeighborEvent) message.obj);
- break;
default:
return false;
}
@@ -1515,9 +1418,6 @@
class UnavailableState extends State {
@Override
public void enter() {
- // TODO: move mIpNeighborMonitor.stop() to TetheredState#exit, and trigger a neighbours
- // dump after starting mIpNeighborMonitor.
- mIpNeighborMonitor.stop();
mLastError = TETHER_ERROR_NO_ERROR;
sendInterfaceState(STATE_UNAVAILABLE);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 81e18ab..5c853f4 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -69,7 +69,6 @@
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetworkStackConstants;
import com.android.net.module.util.SharedLog;
-import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
@@ -77,6 +76,9 @@
import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.net.module.util.ip.ConntrackMonitor;
import com.android.net.module.util.ip.ConntrackMonitor.ConntrackEventConsumer;
+import com.android.net.module.util.ip.IpNeighborMonitor;
+import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent;
+import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEventConsumer;
import com.android.net.module.util.netlink.ConntrackMessage;
import com.android.net.module.util.netlink.NetlinkConstants;
import com.android.net.module.util.netlink.NetlinkUtils;
@@ -182,6 +184,10 @@
private final BpfCoordinatorShim mBpfCoordinatorShim;
@NonNull
private final BpfConntrackEventConsumer mBpfConntrackEventConsumer;
+ @NonNull
+ private final IpNeighborMonitor mIpNeighborMonitor;
+ @NonNull
+ private final BpfNeighborEventConsumer mBpfNeighborEventConsumer;
// True if BPF offload is supported, false otherwise. The BPF offload could be disabled by
// a runtime resource overlay package or device configuration. This flag is only initialized
@@ -190,14 +196,6 @@
// to make it simpler. See also TetheringConfiguration.
private final boolean mIsBpfEnabled;
- // Tracks whether BPF tethering is started or not. This is set by tethering before it
- // starts the first IpServer and is cleared by tethering shortly before the last IpServer
- // is stopped. Note that rule updates (especially deletions, but sometimes additions as
- // well) may arrive when this is false. If they do, they must be communicated to netd.
- // Changes in data limits may also arrive when this is false, and if they do, they must
- // also be communicated to netd.
- private boolean mPollingStarted = false;
-
// Tracking remaining alert quota. Unlike limit quota is subject to interface, the alert
// quota is interface independent and global for tether offload.
private long mRemainingAlertQuota = QUOTA_UNLIMITED;
@@ -280,9 +278,6 @@
private final HashMap<IpServer, HashMap<Inet4Address, ClientInfo>>
mTetherClients = new HashMap<>();
- // Set for which downstream is monitoring the conntrack netlink message.
- private final Set<IpServer> mMonitoringIpServers = new HashSet<>();
-
// Map of upstream interface IPv4 address to interface index.
// TODO: consider making the key to be unique because the upstream address is not unique. It
// is okay for now because there have only one upstream generally.
@@ -304,16 +299,19 @@
@Nullable
private UpstreamInfo mIpv4UpstreamInfo = null;
+ // The IpServers that are currently served by BpfCoordinator.
+ private final ArraySet<IpServer> mServedIpServers = new ArraySet<>();
+
// Runnable that used by scheduling next polling of stats.
private final Runnable mScheduledPollingStats = () -> {
updateForwardedStats();
- maybeSchedulePollingStats();
+ schedulePollingStats();
};
// Runnable that used by scheduling next refreshing of conntrack timeout.
private final Runnable mScheduledConntrackTimeoutUpdate = () -> {
refreshAllConntrackTimeouts();
- maybeScheduleConntrackTimeoutUpdate();
+ scheduleConntrackTimeoutUpdate();
};
// TODO: add BpfMap<TetherDownstream64Key, TetherDownstream64Value> retrieving function.
@@ -339,6 +337,11 @@
return new ConntrackMonitor(getHandler(), getSharedLog(), consumer);
}
+ /** Get ip neighbor monitor */
+ @NonNull public IpNeighborMonitor getIpNeighborMonitor(NeighborEventConsumer consumer) {
+ return new IpNeighborMonitor(getHandler(), getSharedLog(), consumer);
+ }
+
/** Get interface information for a given interface. */
@NonNull public InterfaceParams getInterfaceParams(String ifName) {
return InterfaceParams.getByName(ifName);
@@ -486,6 +489,9 @@
mBpfConntrackEventConsumer = new BpfConntrackEventConsumer();
mConntrackMonitor = mDeps.getConntrackMonitor(mBpfConntrackEventConsumer);
+ mBpfNeighborEventConsumer = new BpfNeighborEventConsumer();
+ mIpNeighborMonitor = mDeps.getIpNeighborMonitor(mBpfNeighborEventConsumer);
+
BpfTetherStatsProvider provider = new BpfTetherStatsProvider();
try {
mDeps.getNetworkStatsManager().registerNetworkStatsProvider(
@@ -505,37 +511,25 @@
}
/**
- * Start BPF tethering offload stats polling when the first upstream is started.
+ * Start BPF tethering offload stats and conntrack timeout polling.
* Note that this can be only called on handler thread.
- * TODO: Perhaps check BPF support before starting.
- * TODO: Start the stats polling only if there is any client on the downstream.
*/
- public void startPolling() {
- if (mPollingStarted) return;
+ private void startStatsAndConntrackTimeoutPolling() {
+ schedulePollingStats();
+ scheduleConntrackTimeoutUpdate();
- if (!isUsingBpf()) {
- mLog.i("BPF is not using");
- return;
- }
-
- mPollingStarted = true;
- maybeSchedulePollingStats();
- maybeScheduleConntrackTimeoutUpdate();
-
- mLog.i("Polling started");
+ mLog.i("Polling started.");
}
/**
- * Stop BPF tethering offload stats polling.
+ * Stop BPF tethering offload stats and conntrack timeout polling.
* The data limit cleanup and the tether stats maps cleanup are not implemented here.
* These cleanups rely on all IpServers calling #removeIpv6DownstreamRule. After the
* last rule is removed from the upstream, #removeIpv6DownstreamRule does the cleanup
* functionality.
* Note that this can be only called on handler thread.
*/
- public void stopPolling() {
- if (!mPollingStarted) return;
-
+ private void stopStatsAndConntrackTimeoutPolling() {
// Stop scheduled polling conntrack timeout.
if (mHandler.hasCallbacks(mScheduledConntrackTimeoutUpdate)) {
mHandler.removeCallbacks(mScheduledConntrackTimeoutUpdate);
@@ -545,9 +539,8 @@
mHandler.removeCallbacks(mScheduledPollingStats);
}
updateForwardedStats();
- mPollingStarted = false;
- mLog.i("Polling stopped");
+ mLog.i("Polling stopped.");
}
/**
@@ -568,7 +561,6 @@
/**
* Start conntrack message monitoring.
- * Note that this can be only called on handler thread.
*
* TODO: figure out a better logging for non-interesting conntrack message.
* For example, the following logging is an IPCTNL_MSG_CT_GET message but looks scary.
@@ -588,45 +580,23 @@
* +------------------+--------------------------------------------------------+
* See NetlinkMonitor#handlePacket, NetlinkMessage#parseNfMessage.
*/
- public void startMonitoring(@NonNull final IpServer ipServer) {
+ private void startConntrackMonitoring() {
// TODO: Wrap conntrackMonitor starting function into mBpfCoordinatorShim.
- if (!isUsingBpf() || !mDeps.isAtLeastS()) return;
+ if (!mDeps.isAtLeastS()) return;
- if (mMonitoringIpServers.contains(ipServer)) {
- Log.wtf(TAG, "The same downstream " + ipServer.interfaceName()
- + " should not start monitoring twice.");
- return;
- }
-
- if (mMonitoringIpServers.isEmpty()) {
- mConntrackMonitor.start();
- mLog.i("Monitoring started");
- }
-
- mMonitoringIpServers.add(ipServer);
+ mConntrackMonitor.start();
+ mLog.i("Conntrack monitoring started.");
}
/**
* Stop conntrack event monitoring.
- * Note that this can be only called on handler thread.
*/
- public void stopMonitoring(@NonNull final IpServer ipServer) {
+ private void stopConntrackMonitoring() {
// TODO: Wrap conntrackMonitor stopping function into mBpfCoordinatorShim.
- if (!isUsingBpf() || !mDeps.isAtLeastS()) return;
-
- // Ignore stopping monitoring if the monitor has never started for a given IpServer.
- if (!mMonitoringIpServers.contains(ipServer)) {
- mLog.e("Ignore stopping monitoring because monitoring has never started for "
- + ipServer.interfaceName());
- return;
- }
-
- mMonitoringIpServers.remove(ipServer);
-
- if (!mMonitoringIpServers.isEmpty()) return;
+ if (!mDeps.isAtLeastS()) return;
mConntrackMonitor.stop();
- mLog.i("Monitoring stopped");
+ mLog.i("Conntrack monitoring stopped.");
}
/**
@@ -689,9 +659,8 @@
/**
* Add IPv6 downstream rule.
- * Note that this can be only called on handler thread.
*/
- public void addIpv6DownstreamRule(
+ private void addIpv6DownstreamRule(
@NonNull final IpServer ipServer, @NonNull final Ipv6DownstreamRule rule) {
if (!isUsingBpf()) return;
@@ -707,9 +676,8 @@
/**
* Remove IPv6 downstream rule.
- * Note that this can be only called on handler thread.
*/
- public void removeIpv6DownstreamRule(
+ private void removeIpv6DownstreamRule(
@NonNull final IpServer ipServer, @NonNull final Ipv6DownstreamRule rule) {
if (!isUsingBpf()) return;
@@ -763,9 +731,8 @@
/**
* Delete all upstream and downstream rules for the passed-in IpServer, and if the new upstream
* is nonzero, reapply them to the new upstream.
- * Note that this can be only called on handler thread.
*/
- public void updateAllIpv6Rules(@NonNull final IpServer ipServer,
+ private void updateAllIpv6Rules(@NonNull final IpServer ipServer,
final InterfaceParams interfaceParams, int newUpstreamIfindex,
@NonNull final Set<IpPrefix> newUpstreamPrefixes) {
if (!isUsingBpf()) return;
@@ -887,6 +854,141 @@
}
/**
+ * Register an IpServer (downstream).
+ * Note that this can be only called on handler thread.
+ */
+ public void addIpServer(@NonNull final IpServer ipServer) {
+ if (!isUsingBpf()) return;
+ if (mServedIpServers.contains(ipServer)) {
+ Log.wtf(TAG, "The same downstream " + ipServer.interfaceName()
+ + " should not add twice.");
+ return;
+ }
+
+ // Start monitoring and polling when the first IpServer is added.
+ if (mServedIpServers.isEmpty()) {
+ startStatsAndConntrackTimeoutPolling();
+ startConntrackMonitoring();
+ mIpNeighborMonitor.start();
+ mLog.i("Neighbor monitoring started.");
+ }
+ mServedIpServers.add(ipServer);
+ }
+
+ /**
+ * Unregister an IpServer (downstream).
+ * Note that this can be only called on handler thread.
+ */
+ public void removeIpServer(@NonNull final IpServer ipServer) {
+ if (!isUsingBpf()) return;
+ if (!mServedIpServers.contains(ipServer)) {
+ mLog.e("Ignore removing because IpServer has never started for "
+ + ipServer.interfaceName());
+ return;
+ }
+ mServedIpServers.remove(ipServer);
+
+ // Stop monitoring and polling when the last IpServer is removed.
+ if (mServedIpServers.isEmpty()) {
+ stopStatsAndConntrackTimeoutPolling();
+ stopConntrackMonitoring();
+ mIpNeighborMonitor.stop();
+ mLog.i("Neighbor monitoring stopped.");
+ }
+ }
+
+ /**
+ * Update upstream interface and its prefixes.
+ * Note that this can be only called on handler thread.
+ */
+ public void updateIpv6UpstreamInterface(@NonNull final IpServer ipServer, int upstreamIfindex,
+ @NonNull Set<IpPrefix> upstreamPrefixes) {
+ if (!isUsingBpf()) return;
+
+ // If the upstream interface has changed, remove all rules and re-add them with the new
+ // upstream interface. If upstream is a virtual network, treated as no upstream.
+ final int prevUpstreamIfindex = ipServer.getIpv6UpstreamIfindex();
+ final InterfaceParams interfaceParams = ipServer.getInterfaceParams();
+ final Set<IpPrefix> prevUpstreamPrefixes = ipServer.getIpv6UpstreamPrefixes();
+ if (prevUpstreamIfindex != upstreamIfindex
+ || !prevUpstreamPrefixes.equals(upstreamPrefixes)) {
+ final boolean upstreamSupportsBpf = checkUpstreamSupportsBpf(upstreamIfindex);
+ updateAllIpv6Rules(ipServer, interfaceParams,
+ getInterfaceIndexForRule(upstreamIfindex, upstreamSupportsBpf),
+ upstreamPrefixes);
+ }
+ }
+
+ private boolean checkUpstreamSupportsBpf(int upstreamIfindex) {
+ final String iface = mInterfaceNames.get(upstreamIfindex);
+ return iface != null && !isVcnInterface(iface);
+ }
+
+ private int getInterfaceIndexForRule(int ifindex, boolean supportsBpf) {
+ return supportsBpf ? ifindex : NO_UPSTREAM;
+ }
+
+ // Handles updates to IPv6 downstream rules if a neighbor event is received.
+ private void addOrRemoveIpv6Downstream(@NonNull IpServer ipServer, NeighborEvent e) {
+ // mInterfaceParams must be non-null or the event would not have arrived.
+ if (e == null) return;
+ if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress()
+ || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
+ return;
+ }
+
+ // When deleting rules, we still need to pass a non-null MAC, even though it's ignored.
+ // Do this here instead of in the Ipv6DownstreamRule constructor to ensure that we
+ // never add rules with a null MAC, only delete them.
+ final InterfaceParams interfaceParams = ipServer.getInterfaceParams();
+ if (interfaceParams == null || interfaceParams.macAddr == null) return;
+ final int lastIpv6UpstreamIfindex = ipServer.getIpv6UpstreamIfindex();
+ final boolean isUpstreamSupportsBpf = checkUpstreamSupportsBpf(lastIpv6UpstreamIfindex);
+ MacAddress dstMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
+ Ipv6DownstreamRule rule = new Ipv6DownstreamRule(
+ getInterfaceIndexForRule(lastIpv6UpstreamIfindex, isUpstreamSupportsBpf),
+ interfaceParams.index, (Inet6Address) e.ip, interfaceParams.macAddr, dstMac);
+ if (e.isValid()) {
+ addIpv6DownstreamRule(ipServer, rule);
+ } else {
+ removeIpv6DownstreamRule(ipServer, rule);
+ }
+ }
+
+ private void updateClientInfoIpv4(@NonNull IpServer ipServer, NeighborEvent e) {
+ if (e == null) return;
+ if (!(e.ip instanceof Inet4Address) || e.ip.isMulticastAddress()
+ || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
+ return;
+ }
+
+ InterfaceParams interfaceParams = ipServer.getInterfaceParams();
+ if (interfaceParams == null) return;
+
+ // When deleting clients, IpServer still need to pass a non-null MAC, even though it's
+ // ignored. Do this here instead of in the ClientInfo constructor to ensure that
+ // IpServer never add clients with a null MAC, only delete them.
+ final MacAddress clientMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
+ final ClientInfo clientInfo = new ClientInfo(interfaceParams.index,
+ interfaceParams.macAddr, (Inet4Address) e.ip, clientMac);
+ if (e.isValid()) {
+ tetherOffloadClientAdd(ipServer, clientInfo);
+ } else {
+ tetherOffloadClientRemove(ipServer, clientInfo);
+ }
+ }
+
+ private void handleNeighborEvent(@NonNull IpServer ipServer, NeighborEvent e) {
+ InterfaceParams interfaceParams = ipServer.getInterfaceParams();
+ if (interfaceParams != null
+ && interfaceParams.index == e.ifindex
+ && interfaceParams.hasMacAddress) {
+ addOrRemoveIpv6Downstream(ipServer, e);
+ updateClientInfoIpv4(ipServer, e);
+ }
+ }
+
+ /**
* Clear all forwarding IPv4 rules for a given client.
* Note that this can be only called on handler thread.
*/
@@ -1137,7 +1239,7 @@
// Note that EthernetTetheringTest#isTetherConfigBpfOffloadEnabled relies on
// "mIsBpfEnabled" to check tethering config via dumpsys. Beware of the change if any.
pw.println("mIsBpfEnabled: " + mIsBpfEnabled);
- pw.println("Polling " + (mPollingStarted ? "started" : "not started"));
+ pw.println("Polling " + (mServedIpServers.isEmpty() ? "not started" : "started"));
pw.println("Stats provider " + (mStatsProvider != null
? "registered" : "not registered"));
pw.println("Upstream quota: " + mInterfaceQuotas.toString());
@@ -1343,19 +1445,6 @@
pw.decreaseIndent();
}
- private <K extends Struct, V extends Struct> void dumpRawMap(IBpfMap<K, V> map,
- IndentingPrintWriter pw) throws ErrnoException {
- if (map == null) {
- pw.println("No BPF support");
- return;
- }
- if (map.isEmpty()) {
- pw.println("No entries");
- return;
- }
- map.forEach((k, v) -> pw.println(BpfDump.toBase64EncodedString(k, v)));
- }
-
/**
* Dump raw BPF map into the base64 encoded strings "<base64 key>,<base64 value>".
* Allow to dump only one map path once. For test only.
@@ -1375,16 +1464,16 @@
// TODO: dump downstream4 map.
if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_STATS)) {
try (IBpfMap<TetherStatsKey, TetherStatsValue> statsMap = mDeps.getBpfStatsMap()) {
- dumpRawMap(statsMap, pw);
- } catch (ErrnoException | IOException e) {
+ BpfDump.dumpRawMap(statsMap, pw);
+ } catch (IOException e) {
pw.println("Error dumping stats map: " + e);
}
return;
}
if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_UPSTREAM4)) {
try (IBpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
- dumpRawMap(upstreamMap, pw);
- } catch (ErrnoException | IOException e) {
+ BpfDump.dumpRawMap(upstreamMap, pw);
+ } catch (IOException e) {
pw.println("Error dumping IPv4 map: " + e);
}
return;
@@ -2052,6 +2141,15 @@
}
}
+ @VisibleForTesting
+ private class BpfNeighborEventConsumer implements NeighborEventConsumer {
+ public void accept(NeighborEvent e) {
+ for (IpServer ipServer : mServedIpServers) {
+ handleNeighborEvent(ipServer, e);
+ }
+ }
+ }
+
private boolean isBpfEnabled() {
final TetheringConfiguration config = mDeps.getTetherConfig();
return (config != null) ? config.isBpfOffloadEnabled() : true /* default value */;
@@ -2379,9 +2477,7 @@
});
}
- private void maybeSchedulePollingStats() {
- if (!mPollingStarted) return;
-
+ private void schedulePollingStats() {
if (mHandler.hasCallbacks(mScheduledPollingStats)) {
mHandler.removeCallbacks(mScheduledPollingStats);
}
@@ -2389,9 +2485,7 @@
mHandler.postDelayed(mScheduledPollingStats, getPollingInterval());
}
- private void maybeScheduleConntrackTimeoutUpdate() {
- if (!mPollingStarted) return;
-
+ private void scheduleConntrackTimeoutUpdate() {
if (mHandler.hasCallbacks(mScheduledConntrackTimeoutUpdate)) {
mHandler.removeCallbacks(mScheduledConntrackTimeoutUpdate);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index d85d92f..0ff89d3 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -38,6 +38,7 @@
import static android.net.TetheringManager.TETHERING_INVALID;
import static android.net.TetheringManager.TETHERING_NCM;
import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHERING_WIGIG;
@@ -278,6 +279,7 @@
private TetheredInterfaceRequestShim mBluetoothIfaceRequest;
private String mConfiguredEthernetIface;
private String mConfiguredBluetoothIface;
+ private String mConfiguredVirtualIface;
private EthernetCallback mEthernetCallback;
private TetheredInterfaceCallbackShim mBluetoothCallback;
private SettingsObserver mSettingsObserver;
@@ -719,6 +721,9 @@
case TETHERING_ETHERNET:
result = setEthernetTethering(enable);
break;
+ case TETHERING_VIRTUAL:
+ result = setVirtualMachineTethering(enable);
+ break;
default:
Log.w(TAG, "Invalid tether type.");
result = TETHER_ERROR_UNKNOWN_TYPE;
@@ -972,6 +977,21 @@
}
}
+ private int setVirtualMachineTethering(final boolean enable) {
+ // TODO(340377643): Use bridge ifname when it's introduced, not fixed TAP ifname.
+ if (enable) {
+ mConfiguredVirtualIface = "avf_tap_fixed";
+ enableIpServing(
+ TETHERING_VIRTUAL,
+ mConfiguredVirtualIface,
+ getRequestedState(TETHERING_VIRTUAL));
+ } else if (mConfiguredVirtualIface != null) {
+ ensureIpServerStopped(mConfiguredVirtualIface);
+ mConfiguredVirtualIface = null;
+ }
+ return TETHER_ERROR_NO_ERROR;
+ }
+
void tether(String iface, int requestedState, final IIntResultListener listener) {
mHandler.post(() -> {
try {
@@ -2069,9 +2089,6 @@
chooseUpstreamType(true);
mTryCell = false;
}
-
- // TODO: Check the upstream interface if it is managed by BPF offload.
- mBpfCoordinator.startPolling();
}
@Override
@@ -2085,7 +2102,6 @@
reportUpstreamChanged(null);
mNotificationUpdater.onUpstreamCapabilitiesChanged(null);
}
- mBpfCoordinator.stopPolling();
mTetheringMetrics.cleanup();
}
diff --git a/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
index 078a35f..c236188 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java
@@ -22,7 +22,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo;
+import com.android.net.module.util.SyncStateMachine;
+import com.android.net.module.util.SyncStateMachine.StateInfo;
import java.util.List;
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 120b871..3944a8a 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -16,7 +16,6 @@
package android.net;
-import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.TETHER_PRIVILEGED;
@@ -42,7 +41,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -65,7 +63,6 @@
import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.testutils.HandlerUtils;
@@ -105,7 +102,7 @@
// Used to check if any tethering interface is available. Choose 200ms to be request timeout
// because the average interface requested time on cuttlefish@acloud is around 10ms.
// See TetheredInterfaceRequester.getInterface, isInterfaceForTetheringAvailable.
- private static final int AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS = 200;
+ private static final int SHORT_TIMEOUT_MS = 1000;
private static final int TETHER_REACHABILITY_ATTEMPTS = 20;
protected static final long WAIT_RA_TIMEOUT_MS = 2000;
@@ -154,7 +151,7 @@
private boolean mRunTests;
private HandlerThread mHandlerThread;
private Handler mHandler;
- private TetheredInterfaceRequester mTetheredInterfaceRequester;
+ protected TetheredInterfaceRequester mTetheredInterfaceRequester;
// Late initialization in initTetheringTester().
private TapPacketReader mUpstreamReader;
@@ -245,12 +242,14 @@
maybeUnregisterTetheringEventCallback(mTetheringEventCallback);
mTetheringEventCallback = null;
- runAsShell(NETWORK_SETTINGS, () -> mTetheredInterfaceRequester.release());
setIncludeTestInterfaces(false);
}
@After
public void tearDown() throws Exception {
+ if (mTetheredInterfaceRequester != null) {
+ mTetheredInterfaceRequester.release();
+ }
try {
if (mRunTests) cleanUp();
} finally {
@@ -263,33 +262,17 @@
}
}
- protected static boolean isInterfaceForTetheringAvailable() throws Exception {
- // Before T, all ethernet interfaces could be used for server mode. Instead of
- // waiting timeout, just checking whether the system currently has any
- // ethernet interface is more reliable.
- if (!SdkLevel.isAtLeastT()) {
- return runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS, () -> sEm.isAvailable());
- }
-
+ protected boolean isInterfaceForTetheringAvailable() throws Exception {
// If previous test case doesn't release tethering interface successfully, the other tests
// after that test may be skipped as unexcepted.
// TODO: figure out a better way to check default tethering interface existenion.
- final TetheredInterfaceRequester requester = new TetheredInterfaceRequester();
- try {
- // Use short timeout (200ms) for requesting an existing interface, if any, because
- // it should reurn faster than requesting a new tethering interface. Using default
- // timeout (5000ms, TIMEOUT_MS) may make that total testing time is over 1 minute
- // test module timeout on internal testing.
- // TODO: if this becomes flaky, consider using default timeout (5000ms) and moving
- // this check into #setUpOnce.
- return requester.getInterface(AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS) != null;
- } catch (TimeoutException e) {
- return false;
- } finally {
- runAsShell(NETWORK_SETTINGS, () -> {
- requester.release();
- });
- }
+ // Use short timeout (200ms) for requesting an existing interface, if any, because
+ // it should reurn faster than requesting a new tethering interface. Using default
+ // timeout (5000ms, TIMEOUT_MS) may make that total testing time is over 1 minute
+ // test module timeout on internal testing.
+ // TODO: if this becomes flaky, consider using default timeout (5000ms) and moving
+ // this check into #setUpOnce.
+ return mTetheredInterfaceRequester.isPhysicalInterfaceAvailable(SHORT_TIMEOUT_MS);
}
protected static void setIncludeTestInterfaces(boolean include) {
@@ -304,14 +287,6 @@
});
}
- protected String getTetheredInterface() throws Exception {
- return mTetheredInterfaceRequester.getInterface();
- }
-
- protected CompletableFuture<String> requestTetheredInterface() throws Exception {
- return mTetheredInterfaceRequester.requestInterface();
- }
-
protected static void waitForRouterAdvertisement(TapPacketReader reader, String iface,
long timeoutMs) {
final long deadline = SystemClock.uptimeMillis() + timeoutMs;
@@ -605,6 +580,11 @@
private TetheredInterfaceRequest mRequest;
private final CompletableFuture<String> mFuture = new CompletableFuture<>();
+ TetheredInterfaceRequester() {
+ mRequest = runAsShell(NETWORK_SETTINGS, () ->
+ sEm.requestTetheredInterface(c -> c.run() /* executor */, this));
+ }
+
@Override
public void onAvailable(String iface) {
Log.d(TAG, "Ethernet interface available: " + iface);
@@ -616,28 +596,21 @@
mFuture.completeExceptionally(new IllegalStateException("onUnavailable received"));
}
- public CompletableFuture<String> requestInterface() {
- assertNull("BUG: more than one tethered interface request", mRequest);
- Log.d(TAG, "Requesting tethered interface");
- mRequest = runAsShell(NETWORK_SETTINGS, () ->
- sEm.requestTetheredInterface(c -> c.run() /* executor */, this));
- return mFuture;
- }
-
- public String getInterface(int timeout) throws Exception {
- return requestInterface().get(timeout, TimeUnit.MILLISECONDS);
+ public boolean isPhysicalInterfaceAvailable(int timeout) {
+ try {
+ final String iface = mFuture.get(timeout, TimeUnit.MILLISECONDS);
+ return !iface.startsWith("testtap");
+ } catch (Exception e) {
+ return false;
+ }
}
public String getInterface() throws Exception {
- return getInterface(TIMEOUT_MS);
+ return mFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public void release() {
- if (mRequest != null) {
- mFuture.obtrudeException(new IllegalStateException("Request already released"));
- mRequest.release();
- mRequest = null;
- }
+ runAsShell(NETWORK_SETTINGS, () -> mRequest.release());
}
}
@@ -658,7 +631,10 @@
lp.setLinkAddresses(addresses);
lp.setDnsServers(dnses);
- return runAsShell(MANAGE_TEST_NETWORKS, () -> initTestNetwork(sContext, lp, TIMEOUT_MS));
+ // TODO: initTestNetwork can take up to 15 seconds on a workstation. Investigate when and
+ // why this is the case. It is unclear whether a 30 second timeout is enough when running
+ // these tests in the much slower test infra.
+ return runAsShell(MANAGE_TEST_NETWORKS, () -> initTestNetwork(sContext, lp, 30_000));
}
protected void sendDownloadPacketUdp(@NonNull final InetAddress srcIp,
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index c54d1b4..5c258b2 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -59,7 +59,7 @@
import com.android.testutils.NetworkStackModuleTest;
import com.android.testutils.TapPacketReader;
-import org.junit.BeforeClass;
+import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -75,8 +75,6 @@
import java.util.Collection;
import java.util.List;
import java.util.Random;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@RunWith(AndroidJUnit4.class)
@@ -151,33 +149,14 @@
(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04 /* Address: 1.2.3.4 */
};
- /** Enable/disable tethering once before running the tests. */
- @BeforeClass
- public static void setUpOnce() throws Exception {
- // The first test case may experience tethering restart with IP conflict handling.
- // Tethering would cache the last upstreams so that the next enabled tethering avoids
- // picking up the address that is in conflict with the upstreams. To protect subsequent
- // tests, turn tethering on and off before running them.
- MyTetheringEventCallback callback = null;
- TestNetworkInterface testIface = null;
- assumeTrue(sEm != null);
- try {
- // If the physical ethernet interface is available, do nothing.
- if (isInterfaceForTetheringAvailable()) return;
-
- testIface = createTestInterface();
- setIncludeTestInterfaces(true);
-
- callback = enableEthernetTethering(testIface.getInterfaceName(), null);
- callback.awaitUpstreamChanged(true /* throwTimeoutException */);
- } catch (TimeoutException e) {
- Log.d(TAG, "WARNNING " + e);
- } finally {
- maybeCloseTestInterface(testIface);
- maybeUnregisterTetheringEventCallback(callback);
-
- setIncludeTestInterfaces(false);
- }
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ // TODO: See b/318121782#comment4. Register an ethernet InterfaceStateListener, and wait for
+ // the callback to report client mode. This happens as soon as both
+ // TetheredInterfaceRequester and the tethering code itself have released the interface,
+ // i.e. after stopTethering() has completed.
+ Thread.sleep(3000);
}
@Test
@@ -201,7 +180,7 @@
Log.d(TAG, "Including test interfaces");
setIncludeTestInterfaces(true);
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -223,8 +202,6 @@
// This test requires manipulating packets. Skip if there is a physical Ethernet connected.
assumeFalse(isInterfaceForTetheringAvailable());
- CompletableFuture<String> futureIface = requestTetheredInterface();
-
setIncludeTestInterfaces(true);
TestNetworkInterface downstreamIface = null;
@@ -234,7 +211,7 @@
try {
downstreamIface = createTestInterface();
- final String iface = futureIface.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -264,7 +241,7 @@
try {
downstreamIface = createTestInterface();
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -338,7 +315,7 @@
try {
downstreamIface = createTestInterface();
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
downstreamIface.getInterfaceName(), iface);
@@ -388,7 +365,7 @@
MyTetheringEventCallback tetheringEventCallback = null;
try {
// Get an interface to use.
- final String iface = getTetheredInterface();
+ final String iface = mTetheredInterfaceRequester.getInterface();
// Enable Ethernet tethering and check that it starts.
tetheringEventCallback = enableEthernetTethering(iface, null /* any upstream */);
@@ -509,17 +486,23 @@
// TODO: test BPF offload maps {rule, stats}.
}
- // Test network topology:
- //
- // public network (rawip) private network
- // | UE |
- // +------------+ V +------------+------------+ V +------------+
- // | Sever +---------+ Upstream | Downstream +---------+ Client |
- // +------------+ +------------+------------+ +------------+
- // remote ip public ip private ip
- // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
- //
- private void runUdp4Test() throws Exception {
+
+ /**
+ * Basic IPv4 UDP tethering test. Verify that UDP tethered packets are transferred no matter
+ * using which data path.
+ */
+ @Test
+ public void testTetherUdpV4() throws Exception {
+ // Test network topology:
+ //
+ // public network (rawip) private network
+ // | UE |
+ // +------------+ V +------------+------------+ V +------------+
+ // | Sever +---------+ Upstream | Downstream +---------+ Client |
+ // +------------+ +------------+------------+ +------------+
+ // remote ip public ip private ip
+ // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
+ //
final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
toList(TEST_IP4_DNS));
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
@@ -541,15 +524,6 @@
sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
}
- /**
- * Basic IPv4 UDP tethering test. Verify that UDP tethered packets are transferred no matter
- * using which data path.
- */
- @Test
- public void testTetherUdpV4() throws Exception {
- runUdp4Test();
- }
-
// Test network topology:
//
// public network (rawip) private network
@@ -599,7 +573,7 @@
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
+ // See the same reason in testTetherUdp4().
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
@@ -707,7 +681,7 @@
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
+ // See the same reason in testTetherUdp4().
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
// [1] Send DNS query.
@@ -751,7 +725,7 @@
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
+ // See the same reason in testTetherUdp4().
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index d5d71bc..47aebe8 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -22,19 +22,24 @@
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.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.net.MacAddress;
import android.os.Build;
+import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.ArrayMap;
import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.SingleWriterBpfMap;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -42,10 +47,18 @@
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import java.io.File;
import java.net.InetAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -56,11 +69,26 @@
private static final int TEST_MAP_SIZE = 16;
private static final String TETHER_DOWNSTREAM6_FS_PATH =
"/sys/fs/bpf/tethering/map_test_tether_downstream6_map";
+ private static final String TETHER2_DOWNSTREAM6_FS_PATH =
+ "/sys/fs/bpf/tethering/map_test_tether2_downstream6_map";
+ private static final String TETHER3_DOWNSTREAM6_FS_PATH =
+ "/sys/fs/bpf/tethering/map_test_tether3_downstream6_map";
private ArrayMap<TetherDownstream6Key, Tether6Value> mTestData;
private BpfMap<TetherDownstream6Key, Tether6Value> mTestMap;
+ private final boolean mShouldTestSingleWriterMap;
+
+ @Parameterized.Parameters
+ public static Collection<Boolean> shouldTestSingleWriterMap() {
+ return Arrays.asList(true, false);
+ }
+
+ public BpfMapTest(boolean shouldTestSingleWriterMap) {
+ mShouldTestSingleWriterMap = shouldTestSingleWriterMap;
+ }
+
@BeforeClass
public static void setupOnce() {
System.loadLibrary(getTetheringJniLibraryName());
@@ -82,11 +110,16 @@
initTestMap();
}
- private void initTestMap() throws Exception {
- mTestMap = new BpfMap<>(
- TETHER_DOWNSTREAM6_FS_PATH,
- TetherDownstream6Key.class, Tether6Value.class);
+ private BpfMap<TetherDownstream6Key, Tether6Value> openTestMap() throws Exception {
+ return mShouldTestSingleWriterMap
+ ? SingleWriterBpfMap.getSingleton(TETHER2_DOWNSTREAM6_FS_PATH,
+ TetherDownstream6Key.class, Tether6Value.class)
+ : new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, TetherDownstream6Key.class,
+ Tether6Value.class);
+ }
+ private void initTestMap() throws Exception {
+ mTestMap = openTestMap();
mTestMap.forEach((key, value) -> {
try {
assertTrue(mTestMap.deleteEntry(key));
@@ -125,7 +158,7 @@
assertEquals(OsConstants.EPERM, expected.errno);
}
}
- try (BpfMap writeOnlyMap = new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_WRONLY,
+ try (BpfMap writeOnlyMap = new BpfMap<>(TETHER3_DOWNSTREAM6_FS_PATH, BpfMap.BPF_F_WRONLY,
TetherDownstream6Key.class, Tether6Value.class)) {
assertNotNull(writeOnlyMap);
try {
@@ -357,6 +390,25 @@
}
@Test
+ public void testMapContentsCorrectOnOpen() throws Exception {
+ final BpfMap<TetherDownstream6Key, Tether6Value> map1, map2;
+
+ map1 = openTestMap();
+ map1.clear();
+ for (int i = 0; i < mTestData.size(); i++) {
+ map1.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
+ }
+
+ // We can't close and reopen map1, because close does nothing. Open another map instead.
+ map2 = openTestMap();
+ for (int i = 0; i < mTestData.size(); i++) {
+ assertEquals(mTestData.valueAt(i), map2.getValue(mTestData.keyAt(i)));
+ }
+
+ map1.clear();
+ }
+
+ @Test
public void testInsertOverflow() throws Exception {
final ArrayMap<TetherDownstream6Key, Tether6Value> testData =
new ArrayMap<>();
@@ -396,8 +448,18 @@
}
}
- private static int getNumOpenFds() {
- return new File("/proc/" + Os.getpid() + "/fd").listFiles().length;
+ private static int getNumOpenBpfMapFds() throws Exception {
+ int numFds = 0;
+ File[] openFiles = new File("/proc/self/fd").listFiles();
+ for (int i = 0; i < openFiles.length; i++) {
+ final Path path = openFiles[i].toPath();
+ if (!Files.isSymbolicLink(path)) continue;
+ if ("anon_inode:bpf-map".equals(Files.readSymbolicLink(path).toString())) {
+ numFds++;
+ }
+ }
+ assertNotEquals("Couldn't find any BPF map fds opened by this process", 0, numFds);
+ return numFds;
}
@Test
@@ -406,7 +468,7 @@
// cache, expect that the fd amount is not increased in the iterations.
// See the comment of BpfMap#close.
final int iterations = 1000;
- final int before = getNumOpenFds();
+ final int before = getNumOpenBpfMapFds();
for (int i = 0; i < iterations; i++) {
try (BpfMap<TetherDownstream6Key, Tether6Value> map = new BpfMap<>(
TETHER_DOWNSTREAM6_FS_PATH,
@@ -414,15 +476,74 @@
// do nothing
}
}
- final int after = getNumOpenFds();
+ final int after = getNumOpenBpfMapFds();
// Check that the number of open fds is the same as before.
- // If this exact match becomes flaky, we probably need to distinguish that fd is belong
- // to "bpf-map".
- // ex:
- // $ adb shell ls -all /proc/16196/fd
- // [..] network_stack 64 2022-07-26 22:01:02.300002956 +0800 749 -> anon_inode:bpf-map
- // [..] network_stack 64 2022-07-26 22:01:02.188002956 +0800 75 -> anon_inode:[eventfd]
assertEquals("Fd leak after " + iterations + " iterations: ", before, after);
}
+
+ @Test
+ public void testNullKey() {
+ assertThrows(NullPointerException.class, () ->
+ mTestMap.insertOrReplaceEntry(null, mTestData.valueAt(0)));
+ }
+
+ private void runBenchmarkThread(BpfMap<TetherDownstream6Key, Tether6Value> map,
+ CompletableFuture<Integer> future, int runtimeMs) {
+ int numReads = 0;
+ final Random r = new Random();
+ final long start = SystemClock.elapsedRealtime();
+ final long stop = start + runtimeMs;
+ while (SystemClock.elapsedRealtime() < stop) {
+ try {
+ final Tether6Value v = map.getValue(mTestData.keyAt(r.nextInt(mTestData.size())));
+ assertNotNull(v);
+ numReads++;
+ } catch (Exception e) {
+ future.completeExceptionally(e);
+ return;
+ }
+ }
+ future.complete(numReads);
+ }
+
+ @Test
+ public void testSingleWriterCacheEffectiveness() throws Exception {
+ assumeTrue(mShouldTestSingleWriterMap);
+ // Benchmark parameters.
+ final int timeoutMs = 5_000; // Only hit if threads don't complete.
+ final int benchmarkTimeMs = 2_000;
+ final int minReads = 50;
+ // Local testing on cuttlefish suggests that caching is ~10x faster.
+ // Only require 3x to reduce test flakiness.
+ final int expectedSpeedup = 3;
+
+ final BpfMap cachedMap = SingleWriterBpfMap.getSingleton(TETHER2_DOWNSTREAM6_FS_PATH,
+ TetherDownstream6Key.class, Tether6Value.class);
+ final BpfMap uncachedMap = new BpfMap(TETHER_DOWNSTREAM6_FS_PATH,
+ TetherDownstream6Key.class, Tether6Value.class);
+
+ // Ensure the maps are not empty.
+ for (int i = 0; i < mTestData.size(); i++) {
+ cachedMap.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
+ uncachedMap.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
+ }
+
+ final CompletableFuture<Integer> cachedResult = new CompletableFuture<>();
+ final CompletableFuture<Integer> uncachedResult = new CompletableFuture<>();
+
+ new Thread(() -> runBenchmarkThread(uncachedMap, uncachedResult, benchmarkTimeMs)).start();
+ new Thread(() -> runBenchmarkThread(cachedMap, cachedResult, benchmarkTimeMs)).start();
+
+ final int cached = cachedResult.get(timeoutMs, TimeUnit.MILLISECONDS);
+ final int uncached = uncachedResult.get(timeoutMs, TimeUnit.MILLISECONDS);
+
+ // Uncomment to see benchmark results.
+ // fail("Cached " + cached + ", uncached " + uncached + ": " + cached / uncached +"x");
+
+ assertTrue("Less than " + minReads + "cached reads observed", cached > minReads);
+ assertTrue("Less than " + minReads + "uncached reads observed", uncached > minReads);
+ assertTrue("Cached map not at least " + expectedSpeedup + "x faster",
+ cached > expectedSpeedup * uncached);
+ }
}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index a7064e8..00eb3b1 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -53,7 +53,6 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -94,7 +93,6 @@
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
-import com.android.net.module.util.ip.IpNeighborMonitor;
import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;
@@ -174,7 +172,6 @@
@Mock private IDhcpServer mDhcpServer;
@Mock private DadProxy mDadProxy;
@Mock private RouterAdvertisementDaemon mRaDaemon;
- @Mock private IpNeighborMonitor mIpNeighborMonitor;
@Mock private IpServer.Dependencies mDependencies;
@Mock private PrivateAddressCoordinator mAddressCoordinator;
private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinatorManager =
@@ -213,20 +210,17 @@
mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH;
}
- doReturn(mIpNeighborMonitor).when(mDependencies).getIpNeighborMonitor(any(), any(), any());
-
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(usingBpfOffload);
when(mTetherConfig.useLegacyDhcpServer()).thenReturn(usingLegacyDhcp);
when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH);
when(mBpfCoordinator.isUsingBpfOffload()).thenReturn(usingBpfOffload);
mIpServer = createIpServer(interfaceType);
- verify(mIpNeighborMonitor).start();
mIpServer.start();
// Starting the state machine always puts us in a consistent state and notifies
// the rest of the world that we've changed from an unknown to available state.
mLooper.dispatchAll();
- reset(mNetd, mCallback, mIpNeighborMonitor);
+ reset(mNetd, mCallback);
when(mRaDaemon.start()).thenReturn(true);
}
@@ -242,6 +236,7 @@
throws Exception {
initStateMachine(interfaceType, usingLegacyDhcp, usingBpfOffload);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
+ verify(mBpfCoordinator).addIpServer(mIpServer);
if (upstreamIface != null) {
InterfaceParams interfaceParams = mDependencies.getInterfaceParams(upstreamIface);
assertNotNull("missing upstream interface: " + upstreamIface, interfaceParams);
@@ -250,8 +245,12 @@
lp.setLinkAddresses(upstreamAddresses);
dispatchTetherConnectionChanged(upstreamIface, lp, 0);
Set<IpPrefix> upstreamPrefixes = getTetherableIpv6Prefixes(lp.getLinkAddresses());
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, interfaceParams.index, upstreamPrefixes);
+ // One is called when handling CMD_TETHER_CONNECTION_CHANGED and the other one is called
+ // when upstream's LinkProperties is updated (updateUpstreamIPv6LinkProperties)
+ verify(mBpfCoordinator, times(2)).maybeAddUpstreamToLookupTable(
+ interfaceParams.index, upstreamIface);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, interfaceParams.index, upstreamPrefixes);
}
reset(mNetd, mBpfCoordinator, mCallback, mAddressCoordinator);
when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
@@ -314,8 +313,6 @@
@Test
public void startsOutAvailable() throws Exception {
- when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
- .thenReturn(mIpNeighborMonitor);
mIpServer = createIpServer(TETHERING_BLUETOOTH);
mIpServer.start();
mLooper.dispatchAll();
@@ -557,8 +554,8 @@
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ inOrder.verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
// When tethering stops, upstream interface is set to zero and thus clearing all upstream
// rules. Downstream rules are needed to be cleared explicitly by calling
// BpfCoordinator#clearAllIpv6Rules in TetheredState#exit.
@@ -570,7 +567,7 @@
argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
inOrder.verify(mBpfCoordinator).tetherOffloadClientClear(mIpServer);
- inOrder.verify(mBpfCoordinator).stopMonitoring(mIpServer);
+ inOrder.verify(mBpfCoordinator).removeIpServer(mIpServer);
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
@@ -765,8 +762,8 @@
lp.setInterfaceName(UPSTREAM_IFACE2);
lp.setLinkAddresses(UPSTREAM_ADDRESSES);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES);
reset(mBpfCoordinator);
// Upstream link addresses change result in updating the rules.
@@ -774,8 +771,8 @@
lp2.setInterfaceName(UPSTREAM_IFACE2);
lp2.setLinkAddresses(UPSTREAM_ADDRESSES2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp2, -1);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2);
reset(mBpfCoordinator);
// When the upstream is lost, rules are removed.
@@ -784,53 +781,54 @@
// - processMessage CMD_TETHER_CONNECTION_CHANGED for the upstream is lost.
// - processMessage CMD_IPV6_TETHER_UPDATE for the IPv6 upstream is lost.
// See dispatchTetherConnectionChanged.
- verify(mBpfCoordinator, times(2)).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ verify(mBpfCoordinator, times(2)).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
reset(mBpfCoordinator);
// If the upstream is IPv4-only, no rules are added.
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- verify(mBpfCoordinator, never()).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ verify(mBpfCoordinator, never()).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
reset(mBpfCoordinator);
// Rules are added again once upstream IPv6 connectivity is available.
lp.setInterfaceName(UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
reset(mBpfCoordinator);
// If upstream IPv6 connectivity is lost, rules are removed.
dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
reset(mBpfCoordinator);
// When upstream IPv6 connectivity comes back, rules are added.
lp.setInterfaceName(UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
reset(mBpfCoordinator);
// When the downstream interface goes down, rules are removed.
mIpServer.stop();
mLooper.dispatchAll();
verify(mBpfCoordinator).clearAllIpv6Rules(mIpServer);
- verify(mBpfCoordinator).updateAllIpv6Rules(
- mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ verify(mBpfCoordinator).removeIpServer(mIpServer);
+ verify(mBpfCoordinator).updateIpv6UpstreamInterface(
+ mIpServer, NO_UPSTREAM, NO_PREFIXES);
reset(mBpfCoordinator);
}
@Test
- public void stopNeighborMonitoringWhenInterfaceDown() throws Exception {
+ public void removeIpServerWhenInterfaceDown() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES,
false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD);
mIpServer.stop();
mLooper.dispatchAll();
- verify(mIpNeighborMonitor).stop();
+ verify(mBpfCoordinator).removeIpServer(mIpServer);
}
private LinkProperties buildIpv6OnlyLinkProperties(final String iface) {
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 47ecf58..e54a7e0 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -25,8 +25,6 @@
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkStats.UID_TETHERING;
-import static android.net.TetheringManager.TETHERING_WIFI;
-import static android.net.ip.IpServer.STATE_TETHERED;
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.ETH_P_IPV6;
@@ -79,9 +77,7 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -100,11 +96,9 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkStats;
-import android.net.RoutingCoordinatorManager;
import android.net.TetherOffloadRuleParcel;
import android.net.TetherStatsParcel;
import android.net.ip.IpServer;
-import android.net.ip.RouterAdvertisementDaemon;
import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
@@ -119,12 +113,10 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetworkStackConstants;
-import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.bpf.Tether4Key;
@@ -143,8 +135,6 @@
import com.android.networkstack.tethering.BpfCoordinator.ClientInfo;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6UpstreamRule;
-import com.android.networkstack.tethering.metrics.TetheringMetrics;
-import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -209,11 +199,6 @@
private static final MacAddress MAC_B = MacAddress.fromString("11:22:33:00:00:0b");
private static final MacAddress MAC_NULL = MacAddress.fromString("00:00:00:00:00:00");
- private static final LinkAddress UPSTREAM_ADDRESS = new LinkAddress("2001:db8:0:1234::168/64");
- private static final LinkAddress UPSTREAM_ADDRESS2 = new LinkAddress("2001:db8:0:abcd::168/64");
- private static final Set<LinkAddress> UPSTREAM_ADDRESSES = Set.of(UPSTREAM_ADDRESS);
- private static final Set<LinkAddress> UPSTREAM_ADDRESSES2 =
- Set.of(UPSTREAM_ADDRESS, UPSTREAM_ADDRESS2);
private static final IpPrefix UPSTREAM_PREFIX = new IpPrefix("2001:db8:0:1234::/64");
private static final IpPrefix UPSTREAM_PREFIX2 = new IpPrefix("2001:db8:0:abcd::/64");
private static final Set<IpPrefix> UPSTREAM_PREFIXES = Set.of(UPSTREAM_PREFIX);
@@ -449,13 +434,6 @@
@Mock private TetheringConfiguration mTetherConfig;
@Mock private ConntrackMonitor mConntrackMonitor;
@Mock private IpNeighborMonitor mIpNeighborMonitor;
- @Mock private RouterAdvertisementDaemon mRaDaemon;
- @Mock private IpServer.Dependencies mIpServerDeps;
- @Mock private IpServer.Callback mIpServerCallback;
- @Mock private PrivateAddressCoordinator mAddressCoordinator;
- private final LateSdk<RoutingCoordinatorManager> mRoutingCoordinatorManager =
- new LateSdk<>(SdkLevel.isAtLeastS() ? mock(RoutingCoordinatorManager.class) : null);
- @Mock private TetheringMetrics mTetheringMetrics;
// Late init since methods must be called by the thread that created this object.
private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb;
@@ -573,24 +551,8 @@
@Before public void setUp() {
MockitoAnnotations.initMocks(this);
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(true /* default value */);
-
- // Simulate the behavior of RoutingCoordinator
- if (null != mRoutingCoordinatorManager.value) {
- doAnswer(it -> {
- final String fromIface = (String) it.getArguments()[0];
- final String toIface = (String) it.getArguments()[1];
- mNetd.tetherAddForward(fromIface, toIface);
- mNetd.ipfwdAddInterfaceForward(fromIface, toIface);
- return null;
- }).when(mRoutingCoordinatorManager.value).addInterfaceForward(any(), any());
- doAnswer(it -> {
- final String fromIface = (String) it.getArguments()[0];
- final String toIface = (String) it.getArguments()[1];
- mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface);
- mNetd.tetherRemoveForward(fromIface, toIface);
- return null;
- }).when(mRoutingCoordinatorManager.value).removeInterfaceForward(any(), any());
- }
+ when(mIpServer.getInterfaceParams()).thenReturn(DOWNSTREAM_IFACE_PARAMS);
+ when(mIpServer2.getInterfaceParams()).thenReturn(DOWNSTREAM_IFACE_PARAMS2);
}
private void waitForIdle() {
@@ -603,70 +565,39 @@
when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]);
}
- @NonNull
- private IpServer makeAndStartIpServer(String interfaceName, BpfCoordinator bpfCoordinator)
- throws Exception {
- final LinkAddress testAddress = new LinkAddress("192.168.42.5/24");
- when(mIpServerDeps.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
- when(mIpServerDeps.getInterfaceParams(DOWNSTREAM_IFACE)).thenReturn(
- DOWNSTREAM_IFACE_PARAMS);
- when(mIpServerDeps.getInterfaceParams(UPSTREAM_IFACE)).thenReturn(UPSTREAM_IFACE_PARAMS);
- when(mIpServerDeps.getInterfaceParams(UPSTREAM_IFACE2)).thenReturn(UPSTREAM_IFACE_PARAMS2);
- when(mIpServerDeps.getInterfaceParams(IPSEC_IFACE)).thenReturn(IPSEC_IFACE_PARAMS);
- when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
- anyBoolean())).thenReturn(testAddress);
- when(mRaDaemon.start()).thenReturn(true);
- ArgumentCaptor<NeighborEventConsumer> neighborEventCaptor =
- ArgumentCaptor.forClass(NeighborEventConsumer.class);
- doReturn(mIpNeighborMonitor).when(mIpServerDeps).getIpNeighborMonitor(any(), any(),
- neighborEventCaptor.capture());
- final IpServer ipServer = new IpServer(
- interfaceName, mHandler, TETHERING_WIFI, new SharedLog("test"), mNetd,
- bpfCoordinator, mRoutingCoordinatorManager, mIpServerCallback, mTetherConfig,
- mAddressCoordinator, mTetheringMetrics, mIpServerDeps);
- ipServer.start();
- ipServer.sendMessage(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
- mTestLooper.dispatchAll();
-
- LinkProperties lp = new LinkProperties();
- lp.setInterfaceName(UPSTREAM_IFACE);
- lp.setLinkAddresses(UPSTREAM_ADDRESSES);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, 0);
-
- mNeighborEventConsumer = neighborEventCaptor.getValue();
- return ipServer;
- }
-
- private void dispatchTetherConnectionChanged(IpServer ipServer, String upstreamIface,
- LinkProperties v6lp, int ttlAdjustment) {
- dispatchTetherConnectionChanged(ipServer, upstreamIface);
- ipServer.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, ttlAdjustment, 0, v6lp);
- mTestLooper.dispatchAll();
- }
-
- private void dispatchTetherConnectionChanged(IpServer ipServer, String upstreamIface) {
- final InterfaceSet ifs = (upstreamIface != null) ? new InterfaceSet(upstreamIface) : null;
- ipServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED, ifs);
- mTestLooper.dispatchAll();
+ private void dispatchIpv6UpstreamChanged(BpfCoordinator bpfCoordinator, IpServer ipServer,
+ int upstreamIfindex, String upstreamIface, Set<IpPrefix> upstreamPrefixes) {
+ bpfCoordinator.maybeAddUpstreamToLookupTable(upstreamIfindex, upstreamIface);
+ bpfCoordinator.updateIpv6UpstreamInterface(ipServer, upstreamIfindex, upstreamPrefixes);
+ when(ipServer.getIpv6UpstreamIfindex()).thenReturn(upstreamIfindex);
+ when(ipServer.getIpv6UpstreamPrefixes()).thenReturn(upstreamPrefixes);
}
private void recvNewNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) {
mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_NEWNEIGH, ifindex, addr,
nudState, mac));
- mTestLooper.dispatchAll();
}
private void recvDelNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) {
mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_DELNEIGH, ifindex, addr,
nudState, mac));
- mTestLooper.dispatchAll();
}
@NonNull
private BpfCoordinator makeBpfCoordinator() throws Exception {
+ return makeBpfCoordinator(true /* addDefaultIpServer */);
+ }
+
+ @NonNull
+ private BpfCoordinator makeBpfCoordinator(boolean addDefaultIpServer) throws Exception {
// mStatsManager will be invoked twice if BpfCoordinator is created the second time.
clearInvocations(mStatsManager);
+ ArgumentCaptor<NeighborEventConsumer> neighborCaptor =
+ ArgumentCaptor.forClass(NeighborEventConsumer.class);
+ doReturn(mIpNeighborMonitor).when(mDeps).getIpNeighborMonitor(neighborCaptor.capture());
final BpfCoordinator coordinator = new BpfCoordinator(mDeps);
+ mNeighborEventConsumer = neighborCaptor.getValue();
+ assertNotNull(mNeighborEventConsumer);
mConsumer = coordinator.getBpfConntrackEventConsumerForTesting();
mTetherClients = coordinator.getTetherClientsForTesting();
@@ -681,6 +612,10 @@
mTetherStatsProviderCb = new TestableNetworkStatsProviderCbBinder();
mTetherStatsProvider.setProviderCallbackBinder(mTetherStatsProviderCb);
+ if (addDefaultIpServer) {
+ coordinator.addIpServer(mIpServer);
+ }
+
return coordinator;
}
@@ -1008,7 +943,6 @@
final String mobileIface = "rmnet_data0";
final Integer mobileIfIndex = 100;
- coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// InOrder is required because mBpfStatsMap may be accessed by both
// BpfCoordinator#tetherOffloadRuleAdd and BpfCoordinator#tetherOffloadGetAndClearStats.
@@ -1020,19 +954,18 @@
mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
final Ipv6DownstreamRule downstreamRule = buildTestDownstreamRule(
mobileIfIndex, NEIGH_A, MAC_A);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyAddUpstreamRule(inOrder, upstreamRule);
- coordinator.addIpv6DownstreamRule(mIpServer, downstreamRule);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_A, NUD_REACHABLE, MAC_A);
verifyAddDownstreamRule(inOrder, downstreamRule);
// Removing the last rule on current upstream immediately sends the cleanup stuff to BPF.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0));
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
verifyRemoveDownstreamRule(inOrder, downstreamRule);
verifyRemoveUpstreamRule(inOrder, upstreamRule);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
@@ -1056,7 +989,6 @@
doReturn(usingApiS).when(mDeps).isAtLeastS();
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
final String mobileIface = "rmnet_data0";
final Integer mobileIfIndex = 100;
@@ -1092,7 +1024,6 @@
setupFunctioningNetdInterface();
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
final String wlanIface = "wlan0";
final Integer wlanIfIndex = 100;
@@ -1148,7 +1079,7 @@
// [3] Stop coordinator.
// Shutdown the coordinator and clear the invocation history, especially the
// tetherOffloadGetStats() calls.
- coordinator.stopPolling();
+ coordinator.removeIpServer(mIpServer);
clearStatsInvocations();
// Verify the polling update thread stopped.
@@ -1162,7 +1093,6 @@
setupFunctioningNetdInterface();
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
final String mobileIface = "rmnet_data0";
final Integer mobileIfIndex = 100;
@@ -1350,7 +1280,6 @@
final String mobileIface = "rmnet_data0";
final int mobileIfIndex = 100;
- coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// [1] Default limit.
// Set the unlimited quota as default if the service has never applied a data limit for a
@@ -1358,8 +1287,8 @@
final Ipv6UpstreamRule rule = buildTestUpstreamRule(
mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
final InOrder inOrder = inOrder(mNetd, mBpfUpstream6Map, mBpfLimitMap, mBpfStatsMap);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyAddUpstreamRule(inOrder, rule);
@@ -1395,7 +1324,6 @@
final String mobileIface = "rmnet_data0";
final int mobileIfIndex = 100;
- coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
// Applying a data limit to the current upstream does not take any immediate action.
// The data limit could be only set on an upstream which has rules.
@@ -1408,31 +1336,30 @@
// Adding the first rule on current upstream immediately sends the quota to BPF.
final Ipv6UpstreamRule ruleA = buildTestUpstreamRule(
mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, limit, true /* isInit */);
verifyAddUpstreamRule(inOrder, ruleA);
inOrder.verifyNoMoreInteractions();
// Adding the second rule on current upstream does not send the quota to BPF.
+ coordinator.addIpServer(mIpServer2);
final Ipv6UpstreamRule ruleB = buildTestUpstreamRule(
mobileIfIndex, DOWNSTREAM_IFINDEX2, UPSTREAM_PREFIX, DOWNSTREAM_MAC2);
- coordinator.updateAllIpv6Rules(
- mIpServer2, DOWNSTREAM_IFACE_PARAMS2, mobileIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer2, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES);
verifyAddUpstreamRule(inOrder, ruleB);
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
// Removing the second rule on current upstream does not send the quota to BPF.
- coordinator.updateAllIpv6Rules(
- mIpServer2, DOWNSTREAM_IFACE_PARAMS2, NO_UPSTREAM, NO_PREFIXES);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer2, NO_UPSTREAM, null, NO_PREFIXES);
verifyRemoveUpstreamRule(inOrder, ruleB);
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
// Removing the last rule on current upstream immediately sends the cleanup stuff to BPF.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0));
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
verifyRemoveUpstreamRule(inOrder, ruleA);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
inOrder.verifyNoMoreInteractions();
@@ -1448,8 +1375,6 @@
final String mobileIface = "rmnet_data0";
final Integer ethIfIndex = 100;
final Integer mobileIfIndex = 101;
- coordinator.maybeAddUpstreamToLookupTable(ethIfIndex, ethIface);
- coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface);
final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map, mBpfLimitMap,
mBpfStatsMap);
@@ -1470,14 +1395,14 @@
final Ipv6DownstreamRule ethernetRuleB = buildTestDownstreamRule(
ethIfIndex, NEIGH_B, MAC_B);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, ethIfIndex, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, ethIfIndex, ethIface, UPSTREAM_PREFIXES);
verifyTetherOffloadSetInterfaceQuota(inOrder, ethIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyAddUpstreamRule(inOrder, ethernetUpstreamRule);
- coordinator.addIpv6DownstreamRule(mIpServer, ethernetRuleA);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_A, NUD_REACHABLE, MAC_A);
verifyAddDownstreamRule(inOrder, ethernetRuleA);
- coordinator.addIpv6DownstreamRule(mIpServer, ethernetRuleB);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_B, NUD_REACHABLE, MAC_B);
verifyAddDownstreamRule(inOrder, ethernetRuleB);
// [2] Update the existing rules from Ethernet to cellular.
@@ -1494,8 +1419,8 @@
// Update the existing rules for upstream changes. The rules are removed and re-added one
// by one for updating upstream interface index and prefixes by #tetherOffloadRuleUpdate.
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES2);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, mobileIfIndex, mobileIface, UPSTREAM_PREFIXES2);
verifyRemoveDownstreamRule(inOrder, ethernetRuleA);
verifyRemoveDownstreamRule(inOrder, ethernetRuleB);
verifyRemoveUpstreamRule(inOrder, ethernetUpstreamRule);
@@ -1532,7 +1457,6 @@
// #makeBpfCoordinator for testing.
// See #testBpfDisabledbyNoBpfDownstream6Map.
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
// The tether stats polling task should not be scheduled.
mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
@@ -1549,7 +1473,7 @@
final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1");
final MacAddress mac = MacAddress.fromString("00:00:00:00:00:0a");
final Ipv6DownstreamRule rule = buildTestDownstreamRule(ifIndex, neigh, mac);
- coordinator.addIpv6DownstreamRule(mIpServer, rule);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, neigh, NUD_REACHABLE, mac);
verifyNeverAddDownstreamRule();
LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules =
coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
@@ -1561,7 +1485,7 @@
rules = new LinkedHashMap<Inet6Address, Ipv6DownstreamRule>();
rules.put(rule.address, rule);
coordinator.getIpv6DownstreamRulesForTesting().put(mIpServer, rules);
- coordinator.removeIpv6DownstreamRule(mIpServer, rule);
+ recvNewNeigh(DOWNSTREAM_IFINDEX, neigh, NUD_STALE, mac);
verifyNeverRemoveDownstreamRule();
rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
assertNotNull(rules);
@@ -1575,8 +1499,8 @@
assertEquals(1, rules.size());
// The rule can't be updated.
- coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS,
- rule.upstreamIfindex + 1 /* new */, UPSTREAM_PREFIXES);
+ coordinator.updateIpv6UpstreamInterface(mIpServer, rule.upstreamIfindex + 1 /* new */,
+ UPSTREAM_PREFIXES);
verifyNeverRemoveDownstreamRule();
verifyNeverAddDownstreamRule();
rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
@@ -1753,18 +1677,14 @@
final BpfCoordinator coordinator = makeBpfCoordinator();
// [1] The default polling interval.
- coordinator.startPolling();
assertEquals(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, coordinator.getPollingInterval());
- coordinator.stopPolling();
// [2] Expect the invalid polling interval isn't applied. The valid range of interval is
// DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS..max_long.
for (final int interval
: new int[] {0, 100, DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS - 1}) {
when(mTetherConfig.getOffloadPollInterval()).thenReturn(interval);
- coordinator.startPolling();
assertEquals(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, coordinator.getPollingInterval());
- coordinator.stopPolling();
}
// [3] Set a specific polling interval which is larger than default value.
@@ -1772,7 +1692,6 @@
// approximation is used to verify the scheduled time of the polling thread.
final int pollingInterval = 100_000;
when(mTetherConfig.getOffloadPollInterval()).thenReturn(pollingInterval);
- coordinator.startPolling();
// Expect the specific polling interval to be applied.
assertEquals(pollingInterval, coordinator.getPollingInterval());
@@ -1800,19 +1719,19 @@
public void testStartStopConntrackMonitoring() throws Exception {
setupFunctioningNetdInterface();
- final BpfCoordinator coordinator = makeBpfCoordinator();
+ final BpfCoordinator coordinator = makeBpfCoordinator(false /* addDefaultIpServer */);
// [1] Don't stop monitoring if it has never started.
- coordinator.stopMonitoring(mIpServer);
+ coordinator.removeIpServer(mIpServer);
verify(mConntrackMonitor, never()).stop();
// [2] Start monitoring.
- coordinator.startMonitoring(mIpServer);
+ coordinator.addIpServer(mIpServer);
verify(mConntrackMonitor).start();
clearInvocations(mConntrackMonitor);
// [3] Stop monitoring.
- coordinator.stopMonitoring(mIpServer);
+ coordinator.removeIpServer(mIpServer);
verify(mConntrackMonitor).stop();
}
@@ -1823,12 +1742,12 @@
public void testStartStopConntrackMonitoring_R() throws Exception {
setupFunctioningNetdInterface();
- final BpfCoordinator coordinator = makeBpfCoordinator();
+ final BpfCoordinator coordinator = makeBpfCoordinator(false /* addDefaultIpServer */);
- coordinator.startMonitoring(mIpServer);
+ coordinator.addIpServer(mIpServer);
verify(mConntrackMonitor, never()).start();
- coordinator.stopMonitoring(mIpServer);
+ coordinator.removeIpServer(mIpServer);
verify(mConntrackMonitor, never()).stop();
}
@@ -1837,23 +1756,23 @@
public void testStartStopConntrackMonitoringWithTwoDownstreamIfaces() throws Exception {
setupFunctioningNetdInterface();
- final BpfCoordinator coordinator = makeBpfCoordinator();
+ final BpfCoordinator coordinator = makeBpfCoordinator(false /* addDefaultIpServer */);
// [1] Start monitoring at the first IpServer adding.
- coordinator.startMonitoring(mIpServer);
+ coordinator.addIpServer(mIpServer);
verify(mConntrackMonitor).start();
clearInvocations(mConntrackMonitor);
// [2] Don't start monitoring at the second IpServer adding.
- coordinator.startMonitoring(mIpServer2);
+ coordinator.addIpServer(mIpServer2);
verify(mConntrackMonitor, never()).start();
// [3] Don't stop monitoring if any downstream interface exists.
- coordinator.stopMonitoring(mIpServer2);
+ coordinator.removeIpServer(mIpServer2);
verify(mConntrackMonitor, never()).stop();
// [4] Stop monitoring if no downstream exists.
- coordinator.stopMonitoring(mIpServer);
+ coordinator.removeIpServer(mIpServer);
verify(mConntrackMonitor).stop();
}
@@ -2016,9 +1935,8 @@
public void testAddDevMapRule6() throws Exception {
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
- coordinator.updateAllIpv6Rules(
- mIpServer, DOWNSTREAM_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
eq(new TetherDevValue(UPSTREAM_IFINDEX)));
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX)),
@@ -2027,8 +1945,9 @@
// Adding the second downstream, only the second downstream ifindex is added to DevMap,
// the existing upstream ifindex won't be added again.
- coordinator.updateAllIpv6Rules(
- mIpServer2, DOWNSTREAM_IFACE_PARAMS2, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
+ coordinator.addIpServer(mIpServer2);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer2, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX2)),
eq(new TetherDevValue(DOWNSTREAM_IFINDEX2)));
verify(mBpfDevMap, never()).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
@@ -2087,7 +2006,6 @@
.startMocking();
try {
final BpfCoordinator coordinator = makeBpfCoordinator();
- coordinator.startPolling();
bpfMap.insertEntry(tcpKey, tcpValue);
bpfMap.insertEntry(udpKey, udpValue);
@@ -2116,7 +2034,7 @@
ExtendedMockito.clearInvocations(staticMockMarker(NetlinkUtils.class));
// [3] Don't refresh conntrack timeout if polling stopped.
- coordinator.stopPolling();
+ coordinator.removeIpServer(mIpServer);
mTestLooper.moveTimeForward(CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS);
waitForIdle();
ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkUtils.class));
@@ -2855,8 +2773,9 @@
final int myIfindex = DOWNSTREAM_IFINDEX;
final int notMyIfindex = myIfindex - 1;
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
resetNetdAndBpfMaps();
verifyNoMoreInteractions(mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
@@ -2905,10 +2824,8 @@
resetNetdAndBpfMaps();
InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map);
- LinkProperties lp = new LinkProperties();
- lp.setInterfaceName(UPSTREAM_IFACE2);
- lp.setLinkAddresses(UPSTREAM_ADDRESSES);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp, -1);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_IFACE2, UPSTREAM_PREFIXES);
final Ipv6DownstreamRule ruleA2 = buildTestDownstreamRule(
UPSTREAM_IFINDEX2, NEIGH_A, MAC_A);
final Ipv6DownstreamRule ruleB2 = buildTestDownstreamRule(
@@ -2922,11 +2839,9 @@
verifyNoUpstreamIpv6ForwardingChange(inOrder);
resetNetdAndBpfMaps();
- // Upstream link addresses change result in updating the rules.
- LinkProperties lp2 = new LinkProperties();
- lp2.setInterfaceName(UPSTREAM_IFACE2);
- lp2.setLinkAddresses(UPSTREAM_ADDRESSES2);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp2, -1);
+ // Upstream prefixes change result in updating the rules.
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_IFACE2, UPSTREAM_PREFIXES2);
verifyRemoveDownstreamRule(inOrder, ruleA2);
verifyRemoveDownstreamRule(inOrder, ruleB2);
verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES);
@@ -2936,7 +2851,7 @@
resetNetdAndBpfMaps();
// When the upstream is lost, rules are removed.
- dispatchTetherConnectionChanged(ipServer, null, null, 0);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES2);
verifyRemoveDownstreamRule(ruleA2);
verifyRemoveDownstreamRule(ruleB2);
@@ -2947,7 +2862,7 @@
resetNetdAndBpfMaps();
// If the upstream is IPv4-only, no IPv6 rules are added to BPF map.
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
resetNetdAndBpfMaps();
recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A);
verifyNoUpstreamIpv6ForwardingChange(null);
@@ -2957,8 +2872,8 @@
// Rules can be added again once upstream IPv6 connectivity is available. The existing rules
// with an upstream of NO_UPSTREAM are reapplied.
- lp.setInterfaceName(UPSTREAM_IFACE);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, -1);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
verifyAddDownstreamRule(ruleA);
recvNewNeigh(myIfindex, NEIGH_B, NUD_REACHABLE, MAC_B);
@@ -2966,23 +2881,27 @@
// If upstream IPv6 connectivity is lost, rules are removed.
resetNetdAndBpfMaps();
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, null, 0);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
verifyRemoveDownstreamRule(ruleA);
verifyRemoveDownstreamRule(ruleB);
verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES);
// When upstream IPv6 connectivity comes back, upstream rules are added and downstream rules
// are reapplied.
- lp.setInterfaceName(UPSTREAM_IFACE);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, -1);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES);
verifyAddDownstreamRule(ruleA);
verifyAddDownstreamRule(ruleB);
resetNetdAndBpfMaps();
// When the downstream interface goes down, rules are removed.
- ipServer.stop();
- mTestLooper.dispatchAll();
+ // Simulate receiving CMD_INTERFACE_DOWN in the BaseServingState of IpServer.
+ reset(mIpNeighborMonitor);
+ dispatchIpv6UpstreamChanged(coordinator, mIpServer, NO_UPSTREAM, null, NO_PREFIXES);
+ coordinator.tetherOffloadClientClear(mIpServer);
+ coordinator.removeIpServer(mIpServer);
+
verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES);
verifyRemoveDownstreamRule(ruleA);
verifyRemoveDownstreamRule(ruleB);
@@ -3004,7 +2923,8 @@
// [1] Enable BPF offload.
// A neighbor that is added or deleted causes the rule to be added or removed.
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
resetNetdAndBpfMaps();
recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A);
@@ -3019,10 +2939,8 @@
resetNetdAndBpfMaps();
// Upstream IPv6 connectivity change causes upstream rules change.
- LinkProperties lp2 = new LinkProperties();
- lp2.setInterfaceName(UPSTREAM_IFACE2);
- lp2.setLinkAddresses(UPSTREAM_ADDRESSES2);
- dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp2, 0);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX2, UPSTREAM_IFACE2, UPSTREAM_PREFIXES2);
verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2);
resetNetdAndBpfMaps();
@@ -3030,7 +2948,6 @@
// A neighbor that is added or deleted doesn’t cause the rule to be added or removed.
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false);
final BpfCoordinator coordinator2 = makeBpfCoordinator();
- final IpServer ipServer2 = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator2);
verifyNoUpstreamIpv6ForwardingChange(null);
resetNetdAndBpfMaps();
@@ -3043,7 +2960,8 @@
resetNetdAndBpfMaps();
// Upstream IPv6 connectivity change doesn't cause the rule to be added or removed.
- dispatchTetherConnectionChanged(ipServer2, UPSTREAM_IFACE2, lp2, 0);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer2, UPSTREAM_IFINDEX2, UPSTREAM_IFACE2, NO_PREFIXES);
verifyNoUpstreamIpv6ForwardingChange(null);
verifyNeverRemoveDownstreamRule();
resetNetdAndBpfMaps();
@@ -3053,7 +2971,6 @@
public void doesNotStartIpNeighborMonitorIfBpfOffloadDisabled() throws Exception {
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false);
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
// IP neighbor monitor doesn't start if BPF offload is disabled.
verify(mIpNeighborMonitor, never()).start();
@@ -3062,15 +2979,10 @@
@Test
public void testSkipVirtualNetworkInBpf() throws Exception {
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
- final LinkProperties v6Only = new LinkProperties();
- v6Only.setInterfaceName(IPSEC_IFACE);
- v6Only.setLinkAddresses(UPSTREAM_ADDRESSES);
resetNetdAndBpfMaps();
- dispatchTetherConnectionChanged(ipServer, IPSEC_IFACE, v6Only, 0);
- verify(mNetd).tetherAddForward(DOWNSTREAM_IFACE, IPSEC_IFACE);
- verify(mNetd).ipfwdAddInterfaceForward(DOWNSTREAM_IFACE, IPSEC_IFACE);
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, IPSEC_IFINDEX, IPSEC_IFACE, UPSTREAM_PREFIXES);
verifyNeverAddUpstreamRule();
recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_A, NUD_REACHABLE, MAC_A);
@@ -3080,7 +2992,6 @@
@Test
public void addRemoveTetherClient() throws Exception {
final BpfCoordinator coordinator = makeBpfCoordinator();
- final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator);
final int myIfindex = DOWNSTREAM_IFINDEX;
final int notMyIfindex = myIfindex - 1;
@@ -3089,33 +3000,36 @@
final InetAddress neighLL = InetAddresses.parseNumericAddress("169.254.0.1");
final InetAddress neighMC = InetAddresses.parseNumericAddress("224.0.0.1");
+ dispatchIpv6UpstreamChanged(
+ coordinator, mIpServer, UPSTREAM_IFINDEX, UPSTREAM_IFACE, UPSTREAM_PREFIXES);
+
// Events on other interfaces are ignored.
recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, MAC_A);
- assertNull(mTetherClients.get(ipServer));
+ assertNull(mTetherClients.get(mIpServer));
// Events on this interface are received and sent to BpfCoordinator.
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, MAC_A);
- assertClientInfoExists(ipServer,
+ assertClientInfoExists(mIpServer,
new ClientInfo(myIfindex, DOWNSTREAM_MAC, (Inet4Address) neighA, MAC_A));
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, MAC_B);
- assertClientInfoExists(ipServer,
+ assertClientInfoExists(mIpServer,
new ClientInfo(myIfindex, DOWNSTREAM_MAC, (Inet4Address) neighB, MAC_B));
// Link-local and multicast neighbors are ignored.
recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, MAC_A);
- assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighLL);
+ assertClientInfoDoesNotExist(mIpServer, (Inet4Address) neighLL);
recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, MAC_A);
- assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighMC);
+ assertClientInfoDoesNotExist(mIpServer, (Inet4Address) neighMC);
// A neighbor that is no longer valid causes the client to be removed.
// NUD_FAILED events do not have a MAC address.
recvNewNeigh(myIfindex, neighA, NUD_FAILED, null);
- assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighA);
+ assertClientInfoDoesNotExist(mIpServer, (Inet4Address) neighA);
// A neighbor that is deleted causes the client to be removed.
recvDelNeigh(myIfindex, neighB, NUD_STALE, MAC_B);
// When last client information is deleted, IpServer will be removed from mTetherClients
- assertNull(mTetherClients.get(ipServer));
+ assertNull(mTetherClients.get(mIpServer));
}
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
index f8e98e3..2417385 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt
@@ -19,9 +19,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.State
+import com.android.net.module.util.SyncStateMachine
+import com.android.net.module.util.SyncStateMachine.StateInfo
import com.android.networkstack.tethering.util.StateMachineShim.AsyncStateMachine
import com.android.networkstack.tethering.util.StateMachineShim.Dependencies
-import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo
import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index f3c7de5..1511ee5 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -35,6 +35,7 @@
// this returns 0 iff skb->sk is NULL
static uint64_t (*bpf_get_socket_cookie)(struct __sk_buff* skb) = (void*)BPF_FUNC_get_socket_cookie;
+static uint64_t (*bpf_get_sk_cookie)(struct bpf_sock* sk) = (void*)BPF_FUNC_get_socket_cookie;
static uint32_t (*bpf_get_socket_uid)(struct __sk_buff* skb) = (void*)BPF_FUNC_get_socket_uid;
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index c3acaad..b3cde45 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -97,6 +97,9 @@
DEFINE_BPF_MAP_NO_NETD(ingress_discard_map, HASH, IngressDiscardKey, IngressDiscardValue,
INGRESS_DISCARD_MAP_SIZE)
+DEFINE_BPF_MAP_RW_NETD(lock_array_test_map, ARRAY, uint32_t, bool, 1)
+DEFINE_BPF_MAP_RW_NETD(lock_hash_test_map, HASH, uint32_t, bool, 1)
+
/* never actually used from ebpf */
DEFINE_BPF_MAP_NO_NETD(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE)
@@ -139,6 +142,11 @@
#define DEFINE_NETD_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
DEFINE_NETD_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, KVER_NONE)
+#define DEFINE_NETD_V_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, minKV) \
+ DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, minKV, \
+ KVER_INF, BPFLOADER_MAINLINE_V_VERSION, BPFLOADER_MAX_VER, MANDATORY, \
+ "fs_bpf_netd_readonly", "", LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
+
// programs that only need to be usable by the system server
#define DEFINE_SYS_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, KVER_NONE, KVER_INF, \
@@ -666,13 +674,86 @@
return permissions ? *permissions : BPF_PERMISSION_INTERNET;
}
-DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
+DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet_create", AID_ROOT, AID_ROOT, inet_socket_create,
KVER_4_14)
(struct bpf_sock* sk) {
// A return value of 1 means allow, everything else means deny.
return (get_app_permissions() & BPF_PERMISSION_INTERNET) ? 1 : 0;
}
+DEFINE_NETD_V_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);
+
+ return 1;
+}
+
+static __always_inline inline int check_localhost(struct bpf_sock_addr *ctx) {
+ // See include/uapi/linux/bpf.h:
+ //
+ // struct bpf_sock_addr {
+ // __u32 user_family; // R: 4 byte
+ // __u32 user_ip4; // BE, R: 1,2,4-byte, W: 4-byte
+ // __u32 user_ip6[4]; // BE, R: 1,2,4,8-byte, W: 4,8-byte
+ // __u32 user_port; // BE, R: 1,2,4-byte, W: 4-byte
+ // __u32 family; // R: 4 byte
+ // __u32 type; // R: 4 byte
+ // __u32 protocol; // R: 4 byte
+ // __u32 msg_src_ip4; // BE, R: 1,2,4-byte, W: 4-byte
+ // __u32 msg_src_ip6[4]; // BE, R: 1,2,4,8-byte, W: 4,8-byte
+ // __bpf_md_ptr(struct bpf_sock *, sk);
+ // };
+ return 1;
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("connect4/inet4_connect", AID_ROOT, AID_ROOT, inet4_connect, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("connect6/inet6_connect", AID_ROOT, AID_ROOT, inet6_connect, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("recvmsg4/udp4_recvmsg", AID_ROOT, AID_ROOT, udp4_recvmsg, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("recvmsg6/udp6_recvmsg", AID_ROOT, AID_ROOT, udp6_recvmsg, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("sendmsg4/udp4_sendmsg", AID_ROOT, AID_ROOT, udp4_sendmsg, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("sendmsg6/udp6_sendmsg", AID_ROOT, AID_ROOT, udp6_sendmsg, KVER_4_14)
+(struct bpf_sock_addr *ctx) {
+ return check_localhost(ctx);
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("getsockopt/prog", AID_ROOT, AID_ROOT, getsockopt_prog, KVER_5_4)
+(struct bpf_sockopt *ctx) {
+ // Tell kernel to return 'original' kernel reply (instead of the bpf modified buffer)
+ // This is important if the answer is larger than PAGE_SIZE (max size this bpf hook can provide)
+ ctx->optlen = 0;
+ return 1; // ALLOW
+}
+
+DEFINE_NETD_V_BPF_PROG_KVER("setsockopt/prog", AID_ROOT, AID_ROOT, setsockopt_prog, KVER_5_4)
+(struct bpf_sockopt *ctx) {
+ // Tell kernel to use/process original buffer provided by userspace.
+ // This is important if it is larger than PAGE_SIZE (max size this bpf hook can handle).
+ ctx->optlen = 0;
+ return 1; // ALLOW
+}
+
LICENSE("Apache 2.0");
CRITICAL("Connectivity and netd");
DISABLE_BTF_ON_USER_BUILDS();
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index 8a56b4a..4877a4b 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -155,7 +155,16 @@
ASSERT_STRING_EQUAL(XT_BPF_ALLOWLIST_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilter_allowlist_xtbpf");
ASSERT_STRING_EQUAL(XT_BPF_DENYLIST_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilter_denylist_xtbpf");
-#define CGROUP_SOCKET_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsock_inet_create"
+#define CGROUP_INET_CREATE_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsock_inet_create"
+#define CGROUP_INET_RELEASE_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsockrelease_inet_release"
+#define CGROUP_CONNECT4_PROG_PATH BPF_NETD_PATH "prog_netd_connect4_inet4_connect"
+#define CGROUP_CONNECT6_PROG_PATH BPF_NETD_PATH "prog_netd_connect6_inet6_connect"
+#define CGROUP_UDP4_RECVMSG_PROG_PATH BPF_NETD_PATH "prog_netd_recvmsg4_udp4_recvmsg"
+#define CGROUP_UDP6_RECVMSG_PROG_PATH BPF_NETD_PATH "prog_netd_recvmsg6_udp6_recvmsg"
+#define CGROUP_UDP4_SENDMSG_PROG_PATH BPF_NETD_PATH "prog_netd_sendmsg4_udp4_sendmsg"
+#define CGROUP_UDP6_SENDMSG_PROG_PATH BPF_NETD_PATH "prog_netd_sendmsg6_udp6_sendmsg"
+#define CGROUP_GETSOCKOPT_PROG_PATH BPF_NETD_PATH "prog_netd_getsockopt_prog"
+#define CGROUP_SETSOCKOPT_PROG_PATH BPF_NETD_PATH "prog_netd_setsockopt_prog"
#define TC_BPF_INGRESS_ACCOUNT_PROG_NAME "prog_netd_schedact_ingress_account"
#define TC_BPF_INGRESS_ACCOUNT_PROG_PATH BPF_NETD_PATH TC_BPF_INGRESS_ACCOUNT_PROG_NAME
@@ -261,5 +270,5 @@
static inline bool is_system_uid(uint32_t uid) {
// MIN_SYSTEM_UID is AID_ROOT == 0, so uint32_t is *always* >= 0
// MAX_SYSTEM_UID is AID_NOBODY == 9999, while AID_APP_START == 10000
- return (uid < AID_APP_START);
+ return ((uid % AID_USER_OFFSET) < AID_APP_START);
}
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index fff3512..6a4471c 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -45,6 +45,10 @@
// Used only by TetheringPrivilegedTests, not by production code.
DEFINE_BPF_MAP_GRW(tether_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 16,
TETHERING_GID)
+DEFINE_BPF_MAP_GRW(tether2_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 16,
+ TETHERING_GID)
+DEFINE_BPF_MAP_GRW(tether3_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 16,
+ TETHERING_GID)
// Used only by BpfBitmapTest, not by production code.
DEFINE_BPF_MAP_GRW(bitmap, ARRAY, int, uint64_t, 2, TETHERING_GID)
diff --git a/common/Android.bp b/common/Android.bp
index 5fab146..5fabf41 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -20,6 +20,8 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+build = ["FlaggedApi.bp"]
+
// This is a placeholder comment to avoid merge conflicts
// as the above target may not exist
// depending on the branch
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 55a96ac..b320b61 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -99,3 +99,27 @@
description: "Flag for oem deny chains blocked reasons API"
bug: "328732146"
}
+
+flag {
+ name: "blocked_reason_network_restricted"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for BLOCKED_REASON_NETWORK_RESTRICTED API"
+ bug: "339559837"
+}
+
+flag {
+ name: "net_capability_not_bandwidth_constrained"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED API"
+ bug: "343823469"
+}
+
+flag {
+ name: "tethering_request_virtual"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for introducing TETHERING_VIRTUAL type"
+ bug: "340376953"
+}
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index 0ee2275..f076f5b 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -20,12 +20,12 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-framework_remoteauth_srcs = [":framework-remoteauth-java-sources-udc-compat"]
-framework_remoteauth_api_srcs = [":framework-remoteauth-java-sources"]
+framework_remoteauth_srcs = [":framework-remoteauth-java-sources"]
+framework_remoteauth_api_srcs = []
java_defaults {
name: "enable-remoteauth-targets",
- enabled: false,
+ enabled: true,
}
// Include build rules from Sources.bp
@@ -201,6 +201,9 @@
"com.android.net.thread.flags-aconfig",
"nearby_flags",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// This rule is not used anymore(b/268440216).
diff --git a/framework-t/lint-baseline.xml b/framework-t/lint-baseline.xml
new file mode 100644
index 0000000..4e206ed
--- /dev/null
+++ b/framework-t/lint-baseline.xml
@@ -0,0 +1,1313 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="Field `SERVICE_NAME` is a flagged API and should be inside an `if (ThreadNetworkFlags.threadEnabled())` check (or annotate the surrounding method `registerServiceWrappers` with `@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" ThreadNetworkManager.SERVICE_NAME,"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java"
+ line="101"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Class `ThreadNetworkManager` is a flagged API and should be inside an `if (ThreadNetworkFlags.threadEnabled())` check (or annotate the surrounding method `registerServiceWrappers` with `@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" ThreadNetworkManager.class,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java"
+ line="102"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `ThreadNetworkManager()` is a flagged API and should be inside an `if (ThreadNetworkFlags.threadEnabled())` check (or annotate the surrounding method `registerServiceWrappers` with `@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" return new ThreadNetworkManager(context, managerService);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java"
+ line="106"
+ column="28"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="529"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="573"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="605"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="This is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `handleMessage` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" final String s = getNsdServiceInfoType((DiscoveryRequest) obj);"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1076"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getServiceType()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `getNsdServiceInfoType` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" return r.getServiceType();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1236"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" DiscoveryRequest request = new DiscoveryRequest.Builder(protocolType, serviceType)"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1477"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" DiscoveryRequest request = new DiscoveryRequest.Builder(protocolType, serviceType)"
+ errorLine2=" ^">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1477"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setNetwork()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" DiscoveryRequest request = new DiscoveryRequest.Builder(protocolType, serviceType)"
+ errorLine2=" ^">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1477"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `discoverServices()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" discoverServices(request, executor, listener);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1479"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `Builder()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" new DiscoveryRequest.Builder(protocolType, serviceType).build();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1566"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `build()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `discoverServices` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" new DiscoveryRequest.Builder(protocolType, serviceType).build();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdManager.java"
+ line="1566"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getSubtypes()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `NsdServiceInfo` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" mSubtypes = new ArraySet<>(other.getSubtypes());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdServiceInfo.java"
+ line="106"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setSubtypes()` is a flagged API and should be inside an `if (Flags.nsdSubtypesSupportEnabled())` check (or annotate the surrounding method `createFromParcel` with `@FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" info.setSubtypes(new ArraySet<>(in.createStringArrayList()));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/NsdServiceInfo.java"
+ line="673"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+</issues>
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 48d40e6..b21e22a 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -1782,16 +1782,16 @@
if (!hasServiceName && !hasServiceType && serviceInfo.getPort() == 0) {
return ServiceValidationType.NO_SERVICE;
}
- if (hasServiceName && hasServiceType) {
- if (serviceInfo.getPort() < 0) {
- throw new IllegalArgumentException("Invalid port");
- }
- if (serviceInfo.getPort() == 0) {
- return ServiceValidationType.HAS_SERVICE_ZERO_PORT;
- }
- return ServiceValidationType.HAS_SERVICE;
+ if (!hasServiceName || !hasServiceType) {
+ throw new IllegalArgumentException("The service name or the service type is missing");
}
- throw new IllegalArgumentException("The service name or the service type is missing");
+ if (serviceInfo.getPort() < 0) {
+ throw new IllegalArgumentException("Invalid port");
+ }
+ if (serviceInfo.getPort() == 0) {
+ return ServiceValidationType.HAS_SERVICE_ZERO_PORT;
+ }
+ return ServiceValidationType.HAS_SERVICE;
}
/**
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index 2f675a9..d8cccb2 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -180,8 +180,18 @@
return new ArrayList<>(mHostAddresses);
}
- /** Set the host addresses */
+ /**
+ * Set the host addresses.
+ *
+ * <p>When registering hosts/services, there can only be one registration including address
+ * records for a given hostname.
+ *
+ * <p>For example, if a client registers a service with the hostname "MyHost" and the address
+ * records of 192.168.1.1 and 192.168.1.2, then other registrations for the hostname "MyHost"
+ * must not have any address record, otherwise there will be a conflict.
+ */
public void setHostAddresses(@NonNull List<InetAddress> addresses) {
+ // TODO: b/284905335 - Notify the client when there is a conflict.
mHostAddresses.clear();
mHostAddresses.addAll(addresses);
}
diff --git a/framework/api/current.txt b/framework/api/current.txt
index ef8415c..7bc0cf3 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -337,6 +337,7 @@
field public static final int NET_CAPABILITY_MCX = 23; // 0x17
field public static final int NET_CAPABILITY_MMS = 0; // 0x0
field public static final int NET_CAPABILITY_MMTEL = 33; // 0x21
+ field @FlaggedApi("com.android.net.flags.net_capability_not_bandwidth_constrained") public static final int NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED = 37; // 0x25
field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14
field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb
field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index d233f3e..cd7307f 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -51,6 +51,7 @@
field public static final int BLOCKED_REASON_DOZE = 2; // 0x2
field public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16; // 0x10
field public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 32; // 0x20
+ field @FlaggedApi("com.android.net.flags.blocked_reason_network_restricted") public static final int BLOCKED_REASON_NETWORK_RESTRICTED = 256; // 0x100
field public static final int BLOCKED_REASON_NONE = 0; // 0x0
field @FlaggedApi("com.android.net.flags.blocked_reason_oem_deny_chains") public static final int BLOCKED_REASON_OEM_DENY = 128; // 0x80
field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
diff --git a/framework/lint-baseline.xml b/framework/lint-baseline.xml
index 2c0b15f..dddabef 100644
--- a/framework/lint-baseline.xml
+++ b/framework/lint-baseline.xml
@@ -375,4 +375,279 @@
column="34"/>
</issue>
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_BACKGROUND` is a flagged API and should be inside an `if (Flags.basicBackgroundRestrictionsEnabled())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.BASIC_BACKGROUND_RESTRICTIONS_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" FIREWALL_CHAIN_BACKGROUND"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsConstants.java"
+ line="115"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_ALLOW` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" FIREWALL_CHAIN_METERED_ALLOW"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsConstants.java"
+ line="137"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_USER` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" FIREWALL_CHAIN_METERED_DENY_USER,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsConstants.java"
+ line="146"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_ADMIN` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" FIREWALL_CHAIN_METERED_DENY_ADMIN"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsConstants.java"
+ line="147"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_BACKGROUND` is a flagged API and should be inside an `if (Flags.basicBackgroundRestrictionsEnabled())` check (or annotate the surrounding method `getMatchByFirewallChain` with `@FlaggedApi(Flags.BASIC_BACKGROUND_RESTRICTIONS_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" case FIREWALL_CHAIN_BACKGROUND:"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="133"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_ALLOW` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `getMatchByFirewallChain` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" case FIREWALL_CHAIN_METERED_ALLOW:"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="143"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_USER` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `getMatchByFirewallChain` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" case FIREWALL_CHAIN_METERED_DENY_USER:"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="145"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_ADMIN` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `getMatchByFirewallChain` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" case FIREWALL_CHAIN_METERED_DENY_ADMIN:"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="147"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `BLOCKED_REASON_APP_BACKGROUND` is a flagged API and should be inside an `if (Flags.basicBackgroundRestrictionsEnabled())` check (or annotate the surrounding method `getUidNetworkingBlockedReasons` with `@FlaggedApi(Flags.BASIC_BACKGROUND_RESTRICTIONS_ENABLED) to transfer requirement to caller`)"
+ errorLine1=" blockedReasons |= BLOCKED_REASON_APP_BACKGROUND;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="293"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `BLOCKED_REASON_OEM_DENY` is a flagged API and should be inside an `if (Flags.blockedReasonOemDenyChains())` check (or annotate the surrounding method `getUidNetworkingBlockedReasons` with `@FlaggedApi(Flags.BLOCKED_REASON_OEM_DENY_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" blockedReasons |= BLOCKED_REASON_OEM_DENY;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/BpfNetMapsUtils.java"
+ line="296"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_ALLOW` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `addUidToMeteredNetworkAllowList` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_ALLOW);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="6191"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_ALLOW` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `removeUidFromMeteredNetworkAllowList` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_DENY);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="6214"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_USER` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `addUidToMeteredNetworkDenyList` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DENY);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="6243"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `FIREWALL_CHAIN_METERED_DENY_USER` is a flagged API and should be inside an `if (Flags.meteredNetworkFirewallChains())` check (or annotate the surrounding method `removeUidFromMeteredNetworkDenyList` with `@FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS) to transfer requirement to caller`)"
+ errorLine1=" mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_ALLOW);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java"
+ line="6273"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="767"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" defaultCapabilities |= (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="818"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="849"
+ column="20"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getSubscriptionIds()` is a flagged API and should be inside an `if (Flags.requestRestrictedWifi())` check (or annotate the surrounding method `restrictCapabilitiesForTestNetwork` with `@FlaggedApi(Flags.REQUEST_RESTRICTED_WIFI) to transfer requirement to caller`)"
+ errorLine1=" final Set<Integer> originalSubIds = getSubscriptionIds();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="1254"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `TRANSPORT_SATELLITE` is a flagged API and should be inside an `if (Flags.supportTransportSatellite())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.SUPPORT_TRANSPORT_SATELLITE) to transfer requirement to caller`)"
+ errorLine1=" public static final int MAX_TRANSPORT = TRANSPORT_SATELLITE;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="1383"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `TRANSPORT_SATELLITE` is a flagged API and should be inside an `if (Flags.supportTransportSatellite())` check (or annotate the surrounding method `specifierAcceptableForMultipleTransports` with `@FlaggedApi(Flags.SUPPORT_TRANSPORT_SATELLITE) to transfer requirement to caller`)"
+ errorLine1=" == (1 << TRANSPORT_CELLULAR | 1 << TRANSPORT_SATELLITE);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="1836"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_LOCAL_NETWORK` is a flagged API and should be inside an `if (Flags.netCapabilityLocalNetwork())` check (or annotate the surrounding method `capabilityNameOf` with `@FlaggedApi(Flags.FLAG_NET_CAPABILITY_LOCAL_NETWORK) to transfer requirement to caller`)"
+ errorLine1=" case NET_CAPABILITY_LOCAL_NETWORK: return "LOCAL_NETWORK";"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="2637"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `capabilityNameOf` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" case NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED: return "NOT_BANDWIDTH_CONSTRAINED";"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java"
+ line="2638"
+ column="18"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `TRANSPORT_SATELLITE` is a flagged API and should be inside an `if (Flags.supportTransportSatellite())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.SUPPORT_TRANSPORT_SATELLITE) to transfer requirement to caller`)"
+ errorLine1=" TRANSPORT_SATELLITE"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java"
+ line="80"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED` is a flagged API and should be inside an `if (Flags.netCapabilityNotBandwidthConstrained())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED) to transfer requirement to caller`)"
+ errorLine1=" NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
+ line="291"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getSubscriptionIds()` is a flagged API and should be inside an `if (Flags.requestRestrictedWifi())` check (or annotate the surrounding method `getSubscriptionIds` with `@FlaggedApi(Flags.REQUEST_RESTRICTED_WIFI) to transfer requirement to caller`)"
+ errorLine1=" return networkCapabilities.getSubscriptionIds();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework/src/android/net/NetworkRequest.java"
+ line="887"
+ column="16"/>
+ </issue>
+
</issues>
\ No newline at end of file
diff --git a/framework/src/android/net/BpfNetMapsUtils.java b/framework/src/android/net/BpfNetMapsUtils.java
index 4099e2a..282a11e 100644
--- a/framework/src/android/net/BpfNetMapsUtils.java
+++ b/framework/src/android/net/BpfNetMapsUtils.java
@@ -68,6 +68,7 @@
import android.os.Build;
import android.os.Process;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Pair;
@@ -330,9 +331,9 @@
) {
throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
- // System uid is not blocked by firewall chains, see bpf_progs/netd.c
- // TODO: use UserHandle.isCore() once it is accessible
- if (uid < Process.FIRST_APPLICATION_UID) {
+ // System uids are not blocked by firewall chains, see bpf_progs/netd.c
+ // TODO: b/348513058 - use UserHandle.isCore() once it is accessible
+ if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
return false;
}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 48ed732..ffaf41f 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -28,6 +28,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresApi;
@@ -70,23 +71,29 @@
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.LruCache;
import android.util.Range;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import libcore.net.event.NetworkEventDispatcher;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -132,6 +139,8 @@
"com.android.net.flags.metered_network_firewall_chains";
static final String BLOCKED_REASON_OEM_DENY_CHAINS =
"com.android.net.flags.blocked_reason_oem_deny_chains";
+ static final String BLOCKED_REASON_NETWORK_RESTRICTED =
+ "com.android.net.flags.blocked_reason_network_restricted";
}
/**
@@ -928,6 +937,17 @@
public static final int BLOCKED_REASON_OEM_DENY = 1 << 7;
/**
+ * Flag to indicate that an app does not have permission to access the specified network,
+ * for example, because it does not have the {@link android.Manifest.permission#INTERNET}
+ * permission.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.BLOCKED_REASON_NETWORK_RESTRICTED)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_NETWORK_RESTRICTED = 1 << 8;
+
+ /**
* Flag to indicate that an app is subject to Data saver restrictions that would
* result in its metered network access being blocked.
*
@@ -968,6 +988,7 @@
BLOCKED_REASON_LOW_POWER_STANDBY,
BLOCKED_REASON_APP_BACKGROUND,
BLOCKED_REASON_OEM_DENY,
+ BLOCKED_REASON_NETWORK_RESTRICTED,
BLOCKED_METERED_REASON_DATA_SAVER,
BLOCKED_METERED_REASON_USER_RESTRICTED,
BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -1193,6 +1214,16 @@
})
public @interface FirewallRule {}
+ /** @hide */
+ public static final long FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS = 1L;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(flag = true, prefix = "FEATURE_", value = {
+ FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS
+ })
+ public @interface ConnectivityManagerFeature {}
+
/**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
* case of the static set/getProcessDefaultNetwork methods and from the Network class.
@@ -1206,6 +1237,27 @@
@GuardedBy("mTetheringEventCallbacks")
private TetheringManager mTetheringManager;
+ // Cache of the most recently used NetworkCallback classes (not instances) -> method flags.
+ // 100 is chosen kind arbitrarily as an unlikely number of different types of NetworkCallback
+ // overrides that a process may have, and should generally not be reached (for example, the
+ // system server services.jar has been observed with dexdump to have only 16 when this was
+ // added, and a very large system services app only had 18).
+ // If this number is exceeded, the code will still function correctly, but re-registering
+ // using a network callback class that was used before, but 100+ other classes have been used in
+ // the meantime, will be a bit slower (as slow as the first registration) because
+ // getDeclaredMethodsFlag must re-examine the callback class to determine what methods it
+ // overrides.
+ private static final LruCache<Class<? extends NetworkCallback>, Integer> sMethodFlagsCache =
+ new LruCache<>(100);
+
+ private final Object mEnabledConnectivityManagerFeaturesLock = new Object();
+ // mEnabledConnectivityManagerFeatures is lazy-loaded in this ConnectivityManager instance, but
+ // fetched from ConnectivityService, where it is loaded in ConnectivityService startup, so it
+ // should have consistent values.
+ @GuardedBy("sEnabledConnectivityManagerFeaturesLock")
+ @ConnectivityManagerFeature
+ private Long mEnabledConnectivityManagerFeatures = null;
+
private TetheringManager getTetheringManager() {
synchronized (mTetheringEventCallbacks) {
if (mTetheringManager == null) {
@@ -3963,6 +4015,55 @@
*/
public static class NetworkCallback {
/**
+ * Bitmask of method flags with all flags set.
+ * @hide
+ */
+ public static final int DECLARED_METHODS_ALL = ~0;
+
+ /**
+ * Bitmask of method flags with no flag set.
+ * @hide
+ */
+ public static final int DECLARED_METHODS_NONE = 0;
+
+ // Tracks whether an instance was created via reflection without calling the constructor.
+ private final boolean mConstructorWasCalled;
+
+ /**
+ * Annotation for NetworkCallback methods to verify filtering is configured properly.
+ *
+ * This is only used in tests to ensure that tests fail when a new callback is added, or
+ * callbacks are modified, without updating
+ * {@link NetworkCallbackMethodsHolder#NETWORK_CB_METHODS} properly.
+ * @hide
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ @VisibleForTesting
+ public @interface FilteredCallback {
+ /**
+ * The NetworkCallback.METHOD_* ID of this method.
+ */
+ int methodId();
+
+ /**
+ * The ConnectivityManager.CALLBACK_* message that this method is directly called by.
+ *
+ * If this method is not called by any message, this should be
+ * {@link #CALLBACK_TRANSITIVE_CALLS_ONLY}.
+ */
+ int calledByCallbackId();
+
+ /**
+ * If this method may call other NetworkCallback methods, an array of methods it calls.
+ *
+ * Only direct calls (not transitive calls) should be included. The IDs must be
+ * NetworkCallback.METHOD_* IDs.
+ */
+ int[] mayCall() default {};
+ }
+
+ /**
* No flags associated with this callback.
* @hide
*/
@@ -4025,6 +4126,7 @@
throw new IllegalArgumentException("Invalid flags");
}
mFlags = flags;
+ mConstructorWasCalled = true;
}
/**
@@ -4042,7 +4144,9 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONPRECHECK, calledByCallbackId = CALLBACK_PRECHECK)
public void onPreCheck(@NonNull Network network) {}
+ private static final int METHOD_ONPRECHECK = 1;
/**
* Called when the framework connects and has declared a new network ready for use.
@@ -4057,6 +4161,11 @@
* @param blocked Whether access to the {@link Network} is blocked due to system policy.
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONAVAILABLE_5ARGS,
+ calledByCallbackId = CALLBACK_AVAILABLE,
+ mayCall = { METHOD_ONAVAILABLE_4ARGS,
+ METHOD_ONLOCALNETWORKINFOCHANGED,
+ METHOD_ONBLOCKEDSTATUSCHANGED_INT })
public final void onAvailable(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities,
@NonNull LinkProperties linkProperties,
@@ -4069,6 +4178,7 @@
if (null != localInfo) onLocalNetworkInfoChanged(network, localInfo);
onBlockedStatusChanged(network, blocked);
}
+ private static final int METHOD_ONAVAILABLE_5ARGS = 2;
/**
* Legacy variant of onAvailable that takes a boolean blocked reason.
@@ -4081,6 +4191,13 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONAVAILABLE_4ARGS,
+ calledByCallbackId = CALLBACK_TRANSITIVE_CALLS_ONLY,
+ mayCall = { METHOD_ONAVAILABLE_1ARG,
+ METHOD_ONNETWORKSUSPENDED,
+ METHOD_ONCAPABILITIESCHANGED,
+ METHOD_ONLINKPROPERTIESCHANGED
+ })
public void onAvailable(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities,
@NonNull LinkProperties linkProperties,
@@ -4094,6 +4211,7 @@
onLinkPropertiesChanged(network, linkProperties);
// No call to onBlockedStatusChanged here. That is done by the caller.
}
+ private static final int METHOD_ONAVAILABLE_4ARGS = 3;
/**
* Called when the framework connects and has declared a new network ready for use.
@@ -4124,7 +4242,10 @@
*
* @param network The {@link Network} of the satisfying network.
*/
+ @FilteredCallback(methodId = METHOD_ONAVAILABLE_1ARG,
+ calledByCallbackId = CALLBACK_TRANSITIVE_CALLS_ONLY)
public void onAvailable(@NonNull Network network) {}
+ private static final int METHOD_ONAVAILABLE_1ARG = 4;
/**
* Called when the network is about to be lost, typically because there are no outstanding
@@ -4143,7 +4264,9 @@
* connected for graceful handover; note that the network may still
* suffer a hard loss at any time.
*/
+ @FilteredCallback(methodId = METHOD_ONLOSING, calledByCallbackId = CALLBACK_LOSING)
public void onLosing(@NonNull Network network, int maxMsToLive) {}
+ private static final int METHOD_ONLOSING = 5;
/**
* Called when a network disconnects or otherwise no longer satisfies this request or
@@ -4164,7 +4287,9 @@
*
* @param network The {@link Network} lost.
*/
+ @FilteredCallback(methodId = METHOD_ONLOST, calledByCallbackId = CALLBACK_LOST)
public void onLost(@NonNull Network network) {}
+ private static final int METHOD_ONLOST = 6;
/**
* Called if no network is found within the timeout time specified in
@@ -4174,7 +4299,9 @@
* {@link NetworkRequest} will have already been removed and released, as if
* {@link #unregisterNetworkCallback(NetworkCallback)} had been called.
*/
+ @FilteredCallback(methodId = METHOD_ONUNAVAILABLE, calledByCallbackId = CALLBACK_UNAVAIL)
public void onUnavailable() {}
+ private static final int METHOD_ONUNAVAILABLE = 7;
/**
* Called when the network corresponding to this request changes capabilities but still
@@ -4191,8 +4318,11 @@
* @param networkCapabilities The new {@link NetworkCapabilities} for this
* network.
*/
+ @FilteredCallback(methodId = METHOD_ONCAPABILITIESCHANGED,
+ calledByCallbackId = CALLBACK_CAP_CHANGED)
public void onCapabilitiesChanged(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities) {}
+ private static final int METHOD_ONCAPABILITIESCHANGED = 8;
/**
* Called when the network corresponding to this request changes {@link LinkProperties}.
@@ -4207,8 +4337,11 @@
* @param network The {@link Network} whose link properties have changed.
* @param linkProperties The new {@link LinkProperties} for this network.
*/
+ @FilteredCallback(methodId = METHOD_ONLINKPROPERTIESCHANGED,
+ calledByCallbackId = CALLBACK_IP_CHANGED)
public void onLinkPropertiesChanged(@NonNull Network network,
@NonNull LinkProperties linkProperties) {}
+ private static final int METHOD_ONLINKPROPERTIESCHANGED = 9;
/**
* Called when there is a change in the {@link LocalNetworkInfo} for this network.
@@ -4220,8 +4353,11 @@
* @param localNetworkInfo the new {@link LocalNetworkInfo} for this network.
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONLOCALNETWORKINFOCHANGED,
+ calledByCallbackId = CALLBACK_LOCAL_NETWORK_INFO_CHANGED)
public void onLocalNetworkInfoChanged(@NonNull Network network,
@NonNull LocalNetworkInfo localNetworkInfo) {}
+ private static final int METHOD_ONLOCALNETWORKINFOCHANGED = 10;
/**
* Called when the network the framework connected to for this request suspends data
@@ -4240,7 +4376,10 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONNETWORKSUSPENDED,
+ calledByCallbackId = CALLBACK_SUSPENDED)
public void onNetworkSuspended(@NonNull Network network) {}
+ private static final int METHOD_ONNETWORKSUSPENDED = 11;
/**
* Called when the network the framework connected to for this request
@@ -4254,7 +4393,9 @@
*
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONNETWORKRESUMED, calledByCallbackId = CALLBACK_RESUMED)
public void onNetworkResumed(@NonNull Network network) {}
+ private static final int METHOD_ONNETWORKRESUMED = 12;
/**
* Called when access to the specified network is blocked or unblocked.
@@ -4267,7 +4408,10 @@
* @param network The {@link Network} whose blocked status has changed.
* @param blocked The blocked status of this {@link Network}.
*/
+ @FilteredCallback(methodId = METHOD_ONBLOCKEDSTATUSCHANGED_BOOL,
+ calledByCallbackId = CALLBACK_TRANSITIVE_CALLS_ONLY)
public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {}
+ private static final int METHOD_ONBLOCKEDSTATUSCHANGED_BOOL = 13;
/**
* Called when access to the specified network is blocked or unblocked, or the reason for
@@ -4285,10 +4429,14 @@
* @param blocked The blocked status of this {@link Network}.
* @hide
*/
+ @FilteredCallback(methodId = METHOD_ONBLOCKEDSTATUSCHANGED_INT,
+ calledByCallbackId = CALLBACK_BLK_CHANGED,
+ mayCall = { METHOD_ONBLOCKEDSTATUSCHANGED_BOOL })
@SystemApi(client = MODULE_LIBRARIES)
public void onBlockedStatusChanged(@NonNull Network network, @BlockedReason int blocked) {
onBlockedStatusChanged(network, blocked != 0);
}
+ private static final int METHOD_ONBLOCKEDSTATUSCHANGED_INT = 14;
private NetworkRequest networkRequest;
private final int mFlags;
@@ -4316,6 +4464,7 @@
}
}
+ private static final int CALLBACK_TRANSITIVE_CALLS_ONLY = 0;
/** @hide */
public static final int CALLBACK_PRECHECK = 1;
/** @hide */
@@ -4341,9 +4490,11 @@
/** @hide */
public static final int CALLBACK_LOCAL_NETWORK_INFO_CHANGED = 12;
+
/** @hide */
public static String getCallbackName(int whichCallback) {
switch (whichCallback) {
+ case CALLBACK_TRANSITIVE_CALLS_ONLY: return "CALLBACK_TRANSITIVE_CALLS_ONLY";
case CALLBACK_PRECHECK: return "CALLBACK_PRECHECK";
case CALLBACK_AVAILABLE: return "CALLBACK_AVAILABLE";
case CALLBACK_LOSING: return "CALLBACK_LOSING";
@@ -4361,6 +4512,68 @@
}
}
+ /** @hide */
+ @VisibleForTesting
+ public static class NetworkCallbackMethod {
+ @NonNull
+ public final String mName;
+ @NonNull
+ public final Class<?>[] mParameterTypes;
+ // Bitmask of CALLBACK_* that may transitively call this method.
+ public final int mCallbacksCallingThisMethod;
+
+ public NetworkCallbackMethod(@NonNull String name, @NonNull Class<?>[] parameterTypes,
+ int callbacksCallingThisMethod) {
+ mName = name;
+ mParameterTypes = parameterTypes;
+ mCallbacksCallingThisMethod = callbacksCallingThisMethod;
+ }
+ }
+
+ // Holder class for the list of NetworkCallbackMethod. This ensures the list is only created
+ // once on first usage, and not just on ConnectivityManager class initialization.
+ /** @hide */
+ @VisibleForTesting
+ public static class NetworkCallbackMethodsHolder {
+ public static final NetworkCallbackMethod[] NETWORK_CB_METHODS =
+ new NetworkCallbackMethod[] {
+ method("onPreCheck", 1 << CALLBACK_PRECHECK, Network.class),
+ // Note the final overload of onAvailable is not included, since it cannot
+ // match any overridden method.
+ method("onAvailable", 1 << CALLBACK_AVAILABLE, Network.class),
+ method("onAvailable", 1 << CALLBACK_AVAILABLE,
+ Network.class, NetworkCapabilities.class,
+ LinkProperties.class, boolean.class),
+ method("onLosing", 1 << CALLBACK_LOSING, Network.class, int.class),
+ method("onLost", 1 << CALLBACK_LOST, Network.class),
+ method("onUnavailable", 1 << CALLBACK_UNAVAIL),
+ method("onCapabilitiesChanged",
+ 1 << CALLBACK_CAP_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, NetworkCapabilities.class),
+ method("onLinkPropertiesChanged",
+ 1 << CALLBACK_IP_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, LinkProperties.class),
+ method("onLocalNetworkInfoChanged",
+ 1 << CALLBACK_LOCAL_NETWORK_INFO_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, LocalNetworkInfo.class),
+ method("onNetworkSuspended",
+ 1 << CALLBACK_SUSPENDED | 1 << CALLBACK_AVAILABLE, Network.class),
+ method("onNetworkResumed",
+ 1 << CALLBACK_RESUMED, Network.class),
+ method("onBlockedStatusChanged",
+ 1 << CALLBACK_BLK_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, boolean.class),
+ method("onBlockedStatusChanged",
+ 1 << CALLBACK_BLK_CHANGED | 1 << CALLBACK_AVAILABLE,
+ Network.class, int.class),
+ };
+
+ private static NetworkCallbackMethod method(
+ String name, int callbacksCallingThisMethod, Class<?>... args) {
+ return new NetworkCallbackMethod(name, args, callbacksCallingThisMethod);
+ }
+ }
+
private static class CallbackHandler extends Handler {
private static final String TAG = "ConnectivityManager.CallbackHandler";
private static final boolean DBG = false;
@@ -4480,6 +4693,14 @@
if (reqType != TRACK_DEFAULT && reqType != TRACK_SYSTEM_DEFAULT && need == null) {
throw new IllegalArgumentException("null NetworkCapabilities");
}
+
+
+ final boolean useDeclaredMethods = isFeatureEnabled(
+ FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS);
+ // Set all bits if the feature is disabled
+ int declaredMethodsFlag = useDeclaredMethods
+ ? tryGetDeclaredMethodsFlag(callback)
+ : NetworkCallback.DECLARED_METHODS_ALL;
final NetworkRequest request;
final String callingPackageName = mContext.getOpPackageName();
try {
@@ -4496,11 +4717,12 @@
if (reqType == LISTEN) {
request = mService.listenForNetwork(
need, messenger, binder, callbackFlags, callingPackageName,
- getAttributionTag());
+ getAttributionTag(), declaredMethodsFlag);
} else {
request = mService.requestNetwork(
asUid, need, reqType.ordinal(), messenger, timeoutMs, binder,
- legacyType, callbackFlags, callingPackageName, getAttributionTag());
+ legacyType, callbackFlags, callingPackageName, getAttributionTag(),
+ declaredMethodsFlag);
}
if (request != null) {
sCallbacks.put(request, callback);
@@ -4515,6 +4737,122 @@
return request;
}
+ private int tryGetDeclaredMethodsFlag(@NonNull NetworkCallback cb) {
+ if (!cb.mConstructorWasCalled) {
+ // Do not use the optimization if the callback was created via reflection or mocking,
+ // as for example with dexmaker-mockito-inline methods will be instrumented without
+ // using subclasses. This does not catch all cases as it is still possible to call the
+ // constructor when creating mocks, but by default constructors are not called in that
+ // case.
+ return NetworkCallback.DECLARED_METHODS_ALL;
+ }
+ try {
+ return getDeclaredMethodsFlag(cb.getClass());
+ } catch (LinkageError e) {
+ // This may happen if some methods reference inaccessible classes in their arguments
+ // (for example b/261807130).
+ Log.w(TAG, "Could not get methods from NetworkCallback class", e);
+ // Fall through
+ } catch (Throwable e) {
+ // Log.wtf would be best but this is in app process, so the TerribleFailureHandler may
+ // have unknown effects, possibly crashing the app (default behavior on eng builds or
+ // if the WTF_IS_FATAL setting is set).
+ Log.e(TAG, "Unexpected error while getting methods from NetworkCallback class", e);
+ // Fall through
+ }
+ return NetworkCallback.DECLARED_METHODS_ALL;
+ }
+
+ private static int getDeclaredMethodsFlag(@NonNull Class<? extends NetworkCallback> clazz) {
+ final Integer cachedFlags = sMethodFlagsCache.get(clazz);
+ // As this is not synchronized, it is possible that this method will calculate the
+ // flags for a given class multiple times, but that is fine. LruCache itself is thread-safe.
+ if (cachedFlags != null) {
+ return cachedFlags;
+ }
+
+ int flag = 0;
+ // This uses getMethods instead of getDeclaredMethods, to make sure that if A overrides B
+ // that overrides NetworkCallback, A.getMethods also returns methods declared by B.
+ for (Method classMethod : clazz.getMethods()) {
+ final Class<?> declaringClass = classMethod.getDeclaringClass();
+ if (declaringClass == NetworkCallback.class) {
+ // The callback is as defined by NetworkCallback and not overridden
+ continue;
+ }
+ if (declaringClass == Object.class) {
+ // Optimization: no need to try to match callbacks for methods declared by Object
+ continue;
+ }
+ flag |= getCallbackIdsCallingThisMethod(classMethod);
+ }
+
+ if (flag == 0) {
+ // dexmaker-mockito-inline (InlineDexmakerMockMaker), for example for mockito-extended,
+ // modifies bytecode of classes in-place to add hooks instead of creating subclasses,
+ // which would not be detected. When no method is found, fall back to enabling callbacks
+ // for all methods.
+ // This will not catch the case where both NetworkCallback bytecode is modified and a
+ // subclass of NetworkCallback that has some overridden methods are used. But this kind
+ // of bytecode injection is only possible in debuggable processes, with a JVMTI debug
+ // agent attached, so it should not cause real issues.
+ // There may be legitimate cases where an empty callback is filed with no method
+ // overridden, for example requestNetwork(requestForCell, new NetworkCallback()) which
+ // would ensure that one cell network stays up. But there is no way to differentiate
+ // such NetworkCallbacks from a mock that called the constructor, so this code will
+ // register the callback with DECLARED_METHODS_ALL and turn off the optimization in that
+ // case. Apps are not expected to do this often anyway since the usefulness is very
+ // limited.
+ flag = NetworkCallback.DECLARED_METHODS_ALL;
+ }
+ sMethodFlagsCache.put(clazz, flag);
+ return flag;
+ }
+
+ /**
+ * Find out which of the base methods in NetworkCallback will call this method.
+ *
+ * For example, in the case of onLinkPropertiesChanged, this will be
+ * (1 << CALLBACK_IP_CHANGED) | (1 << CALLBACK_AVAILABLE).
+ */
+ private static int getCallbackIdsCallingThisMethod(@NonNull Method method) {
+ for (NetworkCallbackMethod baseMethod : NetworkCallbackMethodsHolder.NETWORK_CB_METHODS) {
+ if (!baseMethod.mName.equals(method.getName())) {
+ continue;
+ }
+ Class<?>[] methodParams = method.getParameterTypes();
+
+ // As per JLS 8.4.8.1., a method m1 must have a subsignature of method m2 to override
+ // it. And as per JLS 8.4.2, this means the erasure of the signature of m2 must be the
+ // same as the signature of m1. Since type erasure is done at compile time, with
+ // reflection the erased types are already observed, so the (erased) parameter types
+ // must be equal.
+ // So for example a method that is identical to a NetworkCallback method, except with
+ // one parameter being a subclass of the parameter in the original method, will never
+ // be called since it is not an override (the erasure of the arguments are not the same)
+ // Therefore, the method is an override only if methodParams is exactly equal to
+ // the base method's parameter types.
+ if (Arrays.equals(baseMethod.mParameterTypes, methodParams)) {
+ return baseMethod.mCallbacksCallingThisMethod;
+ }
+ }
+ return 0;
+ }
+
+ private boolean isFeatureEnabled(@ConnectivityManagerFeature long connectivityManagerFeature) {
+ synchronized (mEnabledConnectivityManagerFeaturesLock) {
+ if (mEnabledConnectivityManagerFeatures == null) {
+ try {
+ mEnabledConnectivityManagerFeatures =
+ mService.getEnabledConnectivityManagerFeatures();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ return (mEnabledConnectivityManagerFeatures & connectivityManagerFeature) != 0;
+ }
+ }
+
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) {
return sendRequestForNetwork(Process.INVALID_UID, need, callback, timeoutMs, reqType,
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index 55c7085..988cc92 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -153,7 +153,8 @@
NetworkRequest requestNetwork(int uid, in NetworkCapabilities networkCapabilities, int reqType,
in Messenger messenger, int timeoutSec, in IBinder binder, int legacy,
- int callbackFlags, String callingPackageName, String callingAttributionTag);
+ int callbackFlags, String callingPackageName, String callingAttributionTag,
+ int declaredMethodsFlag);
NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation, String callingPackageName, String callingAttributionTag);
@@ -162,7 +163,7 @@
NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities,
in Messenger messenger, in IBinder binder, int callbackFlags, String callingPackageName,
- String callingAttributionTag);
+ String callingAttributionTag, int declaredMethodsFlag);
void pendingListenForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation, String callingPackageName,
@@ -259,4 +260,6 @@
void setTestLowTcpPollingTimerForKeepalive(long timeMs);
IBinder getRoutingCoordinatorService();
+
+ long getEnabledConnectivityManagerFeatures();
}
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 45efbfe..6a14bde 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -36,9 +36,11 @@
import android.os.Process;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Range;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BitUtils;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.NetworkCapabilitiesUtils;
@@ -134,6 +136,8 @@
"com.android.net.flags.request_restricted_wifi";
static final String SUPPORT_TRANSPORT_SATELLITE =
"com.android.net.flags.support_transport_satellite";
+ static final String NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED =
+ "com.android.net.flags.net_capability_not_bandwidth_constrained";
}
/**
@@ -458,6 +462,7 @@
NET_CAPABILITY_PRIORITIZE_LATENCY,
NET_CAPABILITY_PRIORITIZE_BANDWIDTH,
NET_CAPABILITY_LOCAL_NETWORK,
+ NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED,
})
public @interface NetCapability { }
@@ -740,7 +745,26 @@
@FlaggedApi(Flags.FLAG_NET_CAPABILITY_LOCAL_NETWORK)
public static final int NET_CAPABILITY_LOCAL_NETWORK = 36;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_LOCAL_NETWORK;
+ /**
+ * Indicates that this is not a bandwidth-constrained network.
+ *
+ * Starting from {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, this capability is by default
+ * set in {@link NetworkRequest}s and true for most networks.
+ *
+ * If a network lacks this capability, it is bandwidth-constrained. Bandwidth constrained
+ * networks cannot support high-bandwidth data transfers and applications that request and use
+ * them must ensure that they limit bandwidth usage to below the values returned by
+ * {@link #getLinkDownstreamBandwidthKbps()} and {@link #getLinkUpstreamBandwidthKbps()} and
+ * limit the frequency of their network usage. If applications perform high-bandwidth data
+ * transfers on constrained networks or perform network access too frequently, the system may
+ * block the app's access to the network. The system may take other measures to reduce network
+ * usage on constrained networks, such as disabling network access to apps that are not in the
+ * foreground.
+ */
+ @FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
+ public static final int NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED = 37;
+
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
// Set all bits up to the MAX_NET_CAPABILITY-th bit
private static final long ALL_VALID_CAPABILITIES = (2L << MAX_NET_CAPABILITY) - 1;
@@ -784,10 +808,17 @@
/**
* Capabilities that are set by default when the object is constructed.
*/
- private static final long DEFAULT_CAPABILITIES =
- (1L << NET_CAPABILITY_NOT_RESTRICTED) |
- (1L << NET_CAPABILITY_TRUSTED) |
- (1L << NET_CAPABILITY_NOT_VPN);
+ private static final long DEFAULT_CAPABILITIES;
+ static {
+ long defaultCapabilities =
+ (1L << NET_CAPABILITY_NOT_RESTRICTED)
+ | (1L << NET_CAPABILITY_TRUSTED)
+ | (1L << NET_CAPABILITY_NOT_VPN);
+ if (SdkLevel.isAtLeastV()) {
+ defaultCapabilities |= (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+ }
+ DEFAULT_CAPABILITIES = defaultCapabilities;
+ }
/**
* Capabilities that are managed by ConnectivityService.
@@ -814,7 +845,9 @@
(1L << NET_CAPABILITY_NOT_ROAMING) |
(1L << NET_CAPABILITY_NOT_CONGESTED) |
(1L << NET_CAPABILITY_NOT_SUSPENDED) |
- (1L << NET_CAPABILITY_NOT_VCN_MANAGED);
+ (1L << NET_CAPABILITY_NOT_VCN_MANAGED) |
+ (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+
/**
* Extra allowed capabilities for test networks that do not have TRANSPORT_CELLULAR. Test
@@ -843,7 +876,10 @@
// If the given capability was previously added to the list of forbidden capabilities
// then the capability will also be removed from the list of forbidden capabilities.
// TODO: Add forbidden capabilities to the public API
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG, "addCapability is called with invalid capability: " + capability);
+ return this;
+ }
mNetworkCapabilities |= 1L << capability;
// remove from forbidden capability list
mForbiddenNetworkCapabilities &= ~(1L << capability);
@@ -864,7 +900,10 @@
* @hide
*/
public void addForbiddenCapability(@NetCapability int capability) {
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG, "addForbiddenCapability is called with invalid capability: " + capability);
+ return;
+ }
mForbiddenNetworkCapabilities |= 1L << capability;
mNetworkCapabilities &= ~(1L << capability); // remove from requested capabilities
}
@@ -878,7 +917,10 @@
* @hide
*/
public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) {
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG, "removeCapability is called with invalid capability: " + capability);
+ return this;
+ }
final long mask = ~(1L << capability);
mNetworkCapabilities &= mask;
return this;
@@ -893,7 +935,11 @@
* @hide
*/
public @NonNull NetworkCapabilities removeForbiddenCapability(@NetCapability int capability) {
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG,
+ "removeForbiddenCapability is called with invalid capability: " + capability);
+ return this;
+ }
mForbiddenNetworkCapabilities &= ~(1L << capability);
return this;
}
@@ -2589,6 +2635,7 @@
case NET_CAPABILITY_PRIORITIZE_LATENCY: return "PRIORITIZE_LATENCY";
case NET_CAPABILITY_PRIORITIZE_BANDWIDTH: return "PRIORITIZE_BANDWIDTH";
case NET_CAPABILITY_LOCAL_NETWORK: return "LOCAL_NETWORK";
+ case NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED: return "NOT_BANDWIDTH_CONSTRAINED";
default: return Integer.toString(capability);
}
}
@@ -2632,12 +2679,6 @@
return capability >= 0 && capability <= MAX_NET_CAPABILITY;
}
- private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) {
- if (!isValidCapability(capability)) {
- throw new IllegalArgumentException("NetworkCapability " + capability + " out of range");
- }
- }
-
private static boolean isValidEnterpriseId(
@NetworkCapabilities.EnterpriseId int enterpriseId) {
return enterpriseId >= NET_ENTERPRISE_ID_1
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index f7600b2..502ac6f 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -20,6 +20,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -286,7 +287,8 @@
NET_CAPABILITY_PARTIAL_CONNECTIVITY,
NET_CAPABILITY_TEMPORARILY_NOT_METERED,
NET_CAPABILITY_TRUSTED,
- NET_CAPABILITY_VALIDATED);
+ NET_CAPABILITY_VALIDATED,
+ NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
private final NetworkCapabilities mNetworkCapabilities;
diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java
index 8efb182..f92cdbb 100644
--- a/framework/src/android/net/apf/ApfCapabilities.java
+++ b/framework/src/android/net/apf/ApfCapabilities.java
@@ -106,18 +106,20 @@
@Override
public int hashCode() {
+ // hashCode it is not implemented in R. Therefore it would be dangerous for
+ // NetworkStack to depend on it.
return Objects.hash(apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
}
/**
* Determines whether the APF interpreter advertises support for the data buffer access opcodes
* LDDW (LoaD Data Word) and STDW (STore Data Word). Full LDDW (LoaD Data Word) and
- * STDW (STore Data Word) support is present from APFv4 on.
+ * STDW (STore Data Word) support is present from APFv3 on.
*
* @return {@code true} if the IWifiStaIface#readApfPacketFilterData is supported.
*/
public boolean hasDataAccess() {
- return apfVersionSupported >= 4;
+ return apfVersionSupported > 2;
}
/**
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index 02ef8b7..51df8ab 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -118,9 +118,23 @@
* @hide
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = 35) // TODO: change to VANILLA_ICE_CREAM.
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
public static final long NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION = 333340911L;
+ /**
+ * Enable caching for TrafficStats#get* APIs.
+ *
+ * Apps targeting Android V or later or running on Android V or later may take up to several
+ * seconds to see the updated results.
+ * Apps targeting lower android SDKs do not see cached result for backward compatibility,
+ * results of TrafficStats#get* APIs are reflecting network statistics immediately.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE = 74210811L;
+
private ConnectivityCompatChanges() {
}
}
diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp
index 4be102c..41a28a0 100644
--- a/nearby/framework/Android.bp
+++ b/nearby/framework/Android.bp
@@ -58,4 +58,7 @@
visibility: [
"//packages/modules/Connectivity/nearby/tests:__subpackages__",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/nearby/framework/lint-baseline.xml b/nearby/framework/lint-baseline.xml
new file mode 100644
index 0000000..e1081ee
--- /dev/null
+++ b/nearby/framework/lint-baseline.xml
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="87"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="95"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="103"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="529"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="573"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1=" @FlaggedApi("com.android.nearby.flags.powered_off_finding")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/nearby/framework/java/android/nearby/NearbyManager.java"
+ line="605"
+ column="17"/>
+ </issue>
+
+</issues>
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
index 832ac03..1e36676 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
@@ -28,9 +28,7 @@
import static org.junit.Assume.assumeTrue;
import android.app.UiAutomation;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.cts.BTAdapterUtils;
+import android.bluetooth.test_utils.EnableBluetoothRule;
import android.content.Context;
import android.location.LocationManager;
import android.nearby.BroadcastCallback;
@@ -58,6 +56,7 @@
import com.android.modules.utils.build.SdkLevel;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,6 +75,9 @@
@RunWith(AndroidJUnit4.class)
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public class NearbyManagerTest {
+
+ @ClassRule public static final EnableBluetoothRule sEnableBluetooth = new EnableBluetoothRule();
+
private static final byte[] SALT = new byte[]{1, 2};
private static final byte[] SECRET_ID = new byte[]{1, 2, 3, 4};
private static final byte[] META_DATA_ENCRYPTION_KEY = new byte[14];
@@ -128,8 +130,6 @@
mContext = InstrumentationRegistry.getContext();
mNearbyManager = mContext.getSystemService(NearbyManager.class);
-
- enableBluetooth();
}
@Test
@@ -281,14 +281,6 @@
assertThrows(SecurityException.class, () -> mNearbyManager.getPoweredOffFindingMode());
}
- private void enableBluetooth() {
- BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
- BluetoothAdapter bluetoothAdapter = manager.getAdapter();
- if (!bluetoothAdapter.isEnabled()) {
- assertThat(BTAdapterUtils.enableAdapter(bluetoothAdapter, mContext)).isTrue();
- }
- }
-
private void enableLocation() {
LocationManager locationManager = mContext.getSystemService(LocationManager.class);
UserHandle user = Process.myUserHandle();
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
index f278695..908bb13 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -70,8 +70,8 @@
// For details of versioned rc files see:
// https://android.googlesource.com/platform/system/core/+/HEAD/init/README.md#versioned-rc-files-within-apexs
prebuilt_etc {
- name: "netbpfload.mainline.rc",
- src: "netbpfload.mainline.rc",
+ name: "netbpfload.33rc",
+ src: "netbpfload.33rc",
filename: "netbpfload.33rc",
installable: false,
}
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index 0e1da93..0d4a5c4 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -54,8 +54,10 @@
namespace android {
namespace bpf {
+using base::StartsWith;
using base::EndsWith;
using std::string;
+using std::vector;
static bool exists(const char* const path) {
int v = access(path, F_OK);
@@ -223,25 +225,96 @@
return 0;
}
-static bool isGSI() {
- // From //system/gsid/libgsi.cpp IsGsiRunning()
- return !access("/metadata/gsi/dsu/booted", F_OK);
+static bool hasGSM() {
+ static string ph = base::GetProperty("gsm.current.phone-type", "");
+ static bool gsm = (ph != "");
+ static bool logged = false;
+ if (!logged) {
+ logged = true;
+ ALOGI("hasGSM(gsm.current.phone-type='%s'): %s", ph.c_str(), gsm ? "true" : "false");
+ }
+ return gsm;
+}
+
+static bool isTV() {
+ if (hasGSM()) return false; // TVs don't do GSM
+
+ static string key = base::GetProperty("ro.oem.key1", "");
+ static bool tv = StartsWith(key, "ATV00");
+ static bool logged = false;
+ if (!logged) {
+ logged = true;
+ ALOGI("isTV(ro.oem.key1='%s'): %s.", key.c_str(), tv ? "true" : "false");
+ }
+ return tv;
+}
+
+static bool isWear() {
+ static string wearSdkStr = base::GetProperty("ro.cw_build.wear_sdk.version", "");
+ static int wearSdkInt = base::GetIntProperty("ro.cw_build.wear_sdk.version", 0);
+ static string buildChars = base::GetProperty("ro.build.characteristics", "");
+ static vector<string> v = base::Tokenize(buildChars, ",");
+ static bool watch = (std::find(v.begin(), v.end(), "watch") != v.end());
+ static bool wear = (wearSdkInt > 0) || watch;
+ static bool logged = false;
+ if (!logged) {
+ logged = true;
+ ALOGI("isWear(ro.cw_build.wear_sdk.version=%d[%s] ro.build.characteristics='%s'): %s",
+ wearSdkInt, wearSdkStr.c_str(), buildChars.c_str(), wear ? "true" : "false");
+ }
+ return wear;
}
static int doLoad(char** argv, char * const envp[]) {
- const int device_api_level = android_get_device_api_level();
- const bool isAtLeastT = (device_api_level >= __ANDROID_API_T__);
- const bool isAtLeastU = (device_api_level >= __ANDROID_API_U__);
- const bool isAtLeastV = (device_api_level >= __ANDROID_API_V__);
+ const bool runningAsRoot = !getuid(); // true iff U QPR3 or V+
+
+ // Any released device will have codename REL instead of a 'real' codename.
+ // For safety: default to 'REL' so we default to unreleased=false on failure.
+ const bool unreleased = (base::GetProperty("ro.build.version.codename", "REL") != "REL");
+
+ // goog/main device_api_level is bumped *way* before aosp/main api level
+ // (the latter only gets bumped during the push of goog/main to aosp/main)
+ //
+ // Since we develop in AOSP, we want it to behave as if it was bumped too.
+ //
+ // Note that AOSP doesn't really have a good api level (for example during
+ // early V dev cycle, it would have *all* of T, some but not all of U, and some V).
+ // One could argue that for our purposes AOSP api level should be infinite or 10000.
+ //
+ // This could also cause api to be increased in goog/main or other branches,
+ // but I can't imagine a case where this would be a problem: the problem
+ // is rather a too low api level, rather than some ill defined high value.
+ // For example as I write this aosp is 34/U, and goog is 35/V,
+ // we want to treat both goog & aosp as 35/V, but it's harmless if we
+ // treat goog as 36 because that value isn't yet defined to mean anything,
+ // and we thus never compare against it.
+ //
+ // Also note that 'android_get_device_api_level()' is what the
+ // //system/core/init/apex_init_util.cpp
+ // apex init .XXrc parsing code uses for XX filtering.
+ //
+ // That code has a hack to bump <35 to 35 (to force aosp/main to parse .35rc),
+ // but could (should?) perhaps be adjusted to match this.
+ const int effective_api_level = android_get_device_api_level() + (int)unreleased;
+ const bool isAtLeastT = (effective_api_level >= __ANDROID_API_T__);
+ const bool isAtLeastU = (effective_api_level >= __ANDROID_API_U__);
+ const bool isAtLeastV = (effective_api_level >= __ANDROID_API_V__);
// last in U QPR2 beta1
const bool has_platform_bpfloader_rc = exists("/system/etc/init/bpfloader.rc");
// first in U QPR2 beta~2
const bool has_platform_netbpfload_rc = exists("/system/etc/init/netbpfload.rc");
- ALOGI("NetBpfLoad (%s) api:%d/%d kver:%07x (%s) rc:%d%d",
- argv[0], android_get_application_target_sdk_version(), device_api_level,
- kernelVersion(), describeArch(),
+ // Version of Network BpfLoader depends on the Android OS version
+ unsigned int bpfloader_ver = 42u; // [42] BPFLOADER_MAINLINE_VERSION
+ 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
+ if (isAtLeastV) ++bpfloader_ver; // [46] BPFLOADER_MAINLINE_V_VERSION
+
+ ALOGI("NetBpfLoad v0.%u (%s) api:%d/%d kver:%07x (%s) uid:%d rc:%d%d",
+ bpfloader_ver, argv[0], android_get_device_api_level(), effective_api_level,
+ kernelVersion(), describeArch(), getuid(),
has_platform_bpfloader_rc, has_platform_netbpfload_rc);
if (!has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
@@ -261,24 +334,36 @@
return 1;
}
+ // both S and T require kernel 4.9 (and eBpf support)
if (isAtLeastT && !isAtLeastKernelVersion(4, 9, 0)) {
ALOGE("Android T requires kernel 4.9.");
return 1;
}
+ // U bumps the kernel requirement up to 4.14
if (isAtLeastU && !isAtLeastKernelVersion(4, 14, 0)) {
ALOGE("Android U requires kernel 4.14.");
return 1;
}
+ // V bumps the kernel requirement up to 4.19
+ // see also: //system/netd/tests/kernel_test.cpp TestKernel419
if (isAtLeastV && !isAtLeastKernelVersion(4, 19, 0)) {
ALOGE("Android V requires kernel 4.19.");
return 1;
}
- if (isAtLeastV && isX86() && !isKernel64Bit()) {
+ // Technically already required by U, but only enforce on V+
+ // see also: //system/netd/tests/kernel_test.cpp TestKernel64Bit
+ if (isAtLeastV && isKernel32Bit() && isAtLeastKernelVersion(5, 16, 0)) {
+ ALOGE("Android V+ platform with 32 bit kernel version >= 5.16.0 is unsupported");
+ if (!isTV()) return 1;
+ }
+
+ // Various known ABI layout issues, particularly wrt. bpf and ipsec/xfrm.
+ if (isAtLeastV && isKernel32Bit() && isX86()) {
ALOGE("Android V requires X86 kernel to be 64-bit.");
- return 1;
+ if (!isTV()) return 1;
}
if (isAtLeastV) {
@@ -304,9 +389,8 @@
#undef REQUIRE
- if (bad && !isGSI()) {
+ if (bad) {
ALOGE("Unsupported kernel version (%07x).", kernelVersion());
- sleep(60);
}
}
@@ -326,12 +410,17 @@
* Some of these have userspace or kernel workarounds/hacks.
* Some of them don't...
* We're going to be removing the hacks.
+ * (for example "ANDROID: xfrm: remove in_compat_syscall() checks").
+ * Note: this check/enforcement only applies to *system* userspace code,
+ * it does not affect unprivileged apps, the 32-on-64 compatibility
+ * problems are AFAIK limited to various CAP_NET_ADMIN protected interfaces.
*
* Additionally the 32-bit kernel jit support is poor,
* and 32-bit userspace on 64-bit kernel bpf ringbuffer compatibility is broken.
*/
ALOGE("64-bit userspace required on 6.2+ kernels.");
- return 1;
+ // Stuff won't work reliably, but exempt TVs & Arm Wear devices
+ if (!isTV() && !(isWear() && isArm())) return 1;
}
// Ensure we can determine the Android build type.
@@ -341,7 +430,9 @@
return 1;
}
- if (isAtLeastV) {
+ if (runningAsRoot) {
+ // Note: writing this proc file requires being root (always the case on V+)
+
// Linux 5.16-rc1 changed the default to 2 (disabled but changeable),
// but we need 0 (enabled)
// (this writeFile is known to fail on at least 4.19, but always defaults to 0 on
@@ -351,6 +442,11 @@
}
if (isAtLeastU) {
+ // Note: writing these proc files requires CAP_NET_ADMIN
+ // and sepolicy which is only present on U+,
+ // on Android T and earlier versions they're written from the 'load_bpf_programs'
+ // trigger (ie. by init itself) instead.
+
// Enable the eBPF JIT -- but do note that on 64-bit kernels it is likely
// already force enabled by the kernel config option BPF_JIT_ALWAYS_ON.
// (Note: this (open) will fail with ENOENT 'No such file or directory' if
@@ -380,12 +476,6 @@
// Thus we need to manually create the /sys/fs/bpf/loader subdirectory.
if (createSysFsBpfSubDir("loader")) return 1;
- // Version of Network BpfLoader depends on the Android OS version
- unsigned int bpfloader_ver = 42u; // [42] BPFLOADER_MAINLINE_VERSION
- if (isAtLeastT) ++bpfloader_ver; // [43] BPFLOADER_MAINLINE_T_VERSION
- if (isAtLeastU) ++bpfloader_ver; // [44] BPFLOADER_MAINLINE_U_VERSION
- if (isAtLeastV) ++bpfloader_ver; // [45] BPFLOADER_MAINLINE_V_VERSION
-
// Load all ELF objects, create programs and maps, and pin them
for (const auto& location : locations) {
if (loadAllElfObjects(bpfloader_ver, location) != 0) {
@@ -408,17 +498,25 @@
return 1;
}
- if (isAtLeastV) {
- ALOGI("done, transferring control to platform bpfloader.");
+ // leave a flag that we're done
+ if (createSysFsBpfSubDir("netd_shared/mainline_done")) return 1;
- const char * args[] = { platformBpfLoader, NULL, };
- execve(args[0], (char**)args, envp);
- ALOGE("FATAL: execve('%s'): %d[%s]", platformBpfLoader, errno, strerror(errno));
- return 1;
+ // platform bpfloader will only succeed when run as root
+ if (!runningAsRoot) {
+ // unreachable on U QPR3+ which always runs netbpfload as root
+
+ ALOGI("mainline done, no need to transfer control to platform bpf loader.");
+ return 0;
}
- ALOGI("mainline done!");
- return 0;
+ // unreachable before U QPR3
+ ALOGI("done, transferring control to platform bpfloader.");
+
+ // platform BpfLoader *needs* to run as root
+ const char * args[] = { platformBpfLoader, NULL, };
+ execve(args[0], (char**)args, envp);
+ ALOGE("FATAL: execve('%s'): %d[%s]", platformBpfLoader, errno, strerror(errno));
+ return 1;
}
} // namespace bpf
@@ -433,6 +531,7 @@
ALOGE("Failed to set bpf.progs_loaded property to 1.");
return 125;
}
+ ALOGI("success.");
return 0;
}
diff --git a/netbpfload/loader.cpp b/netbpfload/loader.cpp
index 2b5f5c7..5141095 100644
--- a/netbpfload/loader.cpp
+++ b/netbpfload/loader.cpp
@@ -165,32 +165,34 @@
* since they are less stable abi/api and may conflict with platform uses of bpf.
*/
sectionType sectionNameTypes[] = {
- {"bind4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
- {"bind6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_BIND},
- {"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB, BPF_ATTACH_TYPE_UNSPEC},
- {"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_ATTACH_TYPE_UNSPEC},
- {"connect4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_CONNECT},
- {"connect6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_CONNECT},
- {"egress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_EGRESS},
- {"getsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_GETSOCKOPT},
- {"ingress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_INGRESS},
- {"lwt_in/", BPF_PROG_TYPE_LWT_IN, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_out/", BPF_PROG_TYPE_LWT_OUT, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_seg6local/", BPF_PROG_TYPE_LWT_SEG6LOCAL, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_xmit/", BPF_PROG_TYPE_LWT_XMIT, BPF_ATTACH_TYPE_UNSPEC},
- {"postbind4/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND},
- {"postbind6/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET6_POST_BIND},
- {"recvmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
- {"recvmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_RECVMSG},
- {"schedact/", BPF_PROG_TYPE_SCHED_ACT, BPF_ATTACH_TYPE_UNSPEC},
- {"schedcls/", BPF_PROG_TYPE_SCHED_CLS, BPF_ATTACH_TYPE_UNSPEC},
- {"sendmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_SENDMSG},
- {"sendmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG},
- {"setsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT},
- {"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER, BPF_ATTACH_TYPE_UNSPEC},
- {"sockops/", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS},
- {"sysctl", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL},
- {"xdp/", BPF_PROG_TYPE_XDP, BPF_ATTACH_TYPE_UNSPEC},
+ {"bind4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
+ {"bind6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_BIND},
+ {"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB, BPF_ATTACH_TYPE_UNSPEC},
+ {"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_ATTACH_TYPE_UNSPEC},
+ {"cgroupsockcreate/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_CREATE},
+ {"cgroupsockrelease/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_RELEASE},
+ {"connect4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_CONNECT},
+ {"connect6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_CONNECT},
+ {"egress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_EGRESS},
+ {"getsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_GETSOCKOPT},
+ {"ingress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_INGRESS},
+ {"lwt_in/", BPF_PROG_TYPE_LWT_IN, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_out/", BPF_PROG_TYPE_LWT_OUT, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_seg6local/", BPF_PROG_TYPE_LWT_SEG6LOCAL, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_xmit/", BPF_PROG_TYPE_LWT_XMIT, BPF_ATTACH_TYPE_UNSPEC},
+ {"postbind4/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND},
+ {"postbind6/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET6_POST_BIND},
+ {"recvmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
+ {"recvmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_RECVMSG},
+ {"schedact/", BPF_PROG_TYPE_SCHED_ACT, BPF_ATTACH_TYPE_UNSPEC},
+ {"schedcls/", BPF_PROG_TYPE_SCHED_CLS, BPF_ATTACH_TYPE_UNSPEC},
+ {"sendmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_SENDMSG},
+ {"sendmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG},
+ {"setsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT},
+ {"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER, BPF_ATTACH_TYPE_UNSPEC},
+ {"sockops/", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS},
+ {"sysctl", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL},
+ {"xdp/", BPF_PROG_TYPE_XDP, BPF_ATTACH_TYPE_UNSPEC},
};
typedef struct {
@@ -736,15 +738,15 @@
domain selinux_context = getDomainFromSelinuxContext(md[i].selinux_context);
if (specified(selinux_context)) {
ALOGI("map %s selinux_context [%-32s] -> %d -> '%s' (%s)", mapNames[i].c_str(),
- md[i].selinux_context, selinux_context, lookupSelinuxContext(selinux_context),
- lookupPinSubdir(selinux_context));
+ md[i].selinux_context, static_cast<int>(selinux_context),
+ lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
}
domain pin_subdir = getDomainFromPinSubdir(md[i].pin_subdir);
if (unrecognized(pin_subdir)) return -ENOTDIR;
if (specified(pin_subdir)) {
ALOGI("map %s pin_subdir [%-32s] -> %d -> '%s'", mapNames[i].c_str(), md[i].pin_subdir,
- pin_subdir, lookupPinSubdir(pin_subdir));
+ static_cast<int>(pin_subdir), lookupPinSubdir(pin_subdir));
}
// Format of pin location is /sys/fs/bpf/<pin_subdir|prefix>map_<objName>_<mapName>
@@ -974,13 +976,14 @@
if (specified(selinux_context)) {
ALOGI("prog %s selinux_context [%-32s] -> %d -> '%s' (%s)", name.c_str(),
- cs[i].prog_def->selinux_context, selinux_context,
+ cs[i].prog_def->selinux_context, static_cast<int>(selinux_context),
lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
}
if (specified(pin_subdir)) {
ALOGI("prog %s pin_subdir [%-32s] -> %d -> '%s'", name.c_str(),
- cs[i].prog_def->pin_subdir, pin_subdir, lookupPinSubdir(pin_subdir));
+ cs[i].prog_def->pin_subdir, static_cast<int>(pin_subdir),
+ lookupPinSubdir(pin_subdir));
}
// strip any potential $foo suffix
diff --git a/netbpfload/netbpfload.33rc b/netbpfload/netbpfload.33rc
new file mode 100644
index 0000000..493731f
--- /dev/null
+++ b/netbpfload/netbpfload.33rc
@@ -0,0 +1,21 @@
+# This file takes effect only on T and U (on V netbpfload.35rc takes priority).
+#
+# The service is started from netd's libnetd_updatable shared library
+# on initial (boot time) startup of netd.
+#
+# However we never start this service on U QPR3.
+#
+# This is due to lack of a need: U QPR2 split the previously single
+# platform bpfloader into platform netbpfload -> platform bpfloader.
+# U QPR3 made the platform netbpfload unconditionally exec apex netbpfload,
+# so by the time U QPR3's netd runs, apex netbpfload is already done.
+
+service mdnsd_netbpfload /apex/com.android.tethering/bin/netbpfload
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ group system root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw
+ user system
+ file /dev/kmsg w
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ reboot_on_failure reboot,netbpfload-failed
+ override
diff --git a/netbpfload/netbpfload.mainline.rc b/netbpfload/netbpfload.mainline.rc
deleted file mode 100644
index d38a503..0000000
--- a/netbpfload/netbpfload.mainline.rc
+++ /dev/null
@@ -1,17 +0,0 @@
-service mdnsd_loadbpf /system/bin/bpfloader
- 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
-
-service bpfloader /apex/com.android.tethering/bin/netbpfload
- capabilities CHOWN SYS_ADMIN NET_ADMIN
- group system root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw
- user system
- file /dev/kmsg w
- rlimit memlock 1073741824 1073741824
- oneshot
- reboot_on_failure reboot,bpfloader-failed
- override
diff --git a/netd/Android.bp b/netd/Android.bp
index eedbdae..fe4d999 100644
--- a/netd/Android.bp
+++ b/netd/Android.bp
@@ -72,6 +72,8 @@
"BpfHandlerTest.cpp",
"BpfBaseTest.cpp",
],
+ version_script: ":connectivity_mainline_test_map",
+ stl: "libc++_static",
static_libs: [
"libbase",
"libnetd_updatable",
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index b535ebf..4779b47 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "BpfHandler"
+#define LOG_TAG "NetdUpdatable"
#include "BpfHandler.h"
@@ -34,6 +34,7 @@
namespace net {
using base::unique_fd;
+using base::WaitForProperty;
using bpf::getSocketCookie;
using bpf::retrieveProgram;
using netdutils::Status;
@@ -85,31 +86,6 @@
return Status("U+ platform with kernel version < 4.14.0 is unsupported");
}
- if (modules::sdklevel::IsAtLeastV()) {
- // V bumps the kernel requirement up to 4.19
- // see also: //system/netd/tests/kernel_test.cpp TestKernel419
- if (!bpf::isAtLeastKernelVersion(4, 19, 0)) {
- return Status("V+ platform with kernel version < 4.19.0 is unsupported");
- }
-
- // Technically already required by U, but only enforce on V+
- // see also: //system/netd/tests/kernel_test.cpp TestKernel64Bit
- if (bpf::isKernel32Bit() && bpf::isAtLeastKernelVersion(5, 16, 0)) {
- return Status("V+ platform with 32 bit kernel, version >= 5.16.0 is unsupported");
- }
- }
-
- // Linux 6.1 is highest version supported by U, starting with V new kernels,
- // ie. 6.2+ we are dropping various kernel/system userspace 32-on-64 hacks
- // (for example "ANDROID: xfrm: remove in_compat_syscall() checks").
- // Note: this check/enforcement only applies to *system* userspace code,
- // it does not affect unprivileged apps, the 32-on-64 compatibility
- // problems are AFAIK limited to various CAP_NET_ADMIN protected interfaces.
- // see also: //system/bpf/bpfloader/BpfLoader.cpp main()
- if (bpf::isUserspace32bit() && bpf::isAtLeastKernelVersion(6, 2, 0)) {
- return Status("32 bit userspace with Kernel version >= 6.2.0 is unsupported");
- }
-
// U mandates this mount point (though it should also be the case on T)
if (modules::sdklevel::IsAtLeastU() && !!strcmp(cg2_path, "/sys/fs/cgroup")) {
return Status("U+ platform with cg2_path != /sys/fs/cgroup is unsupported");
@@ -134,8 +110,35 @@
// TODO: delete the if statement once all devices should support cgroup
// socket filter (ie. the minimum kernel version required is 4.14).
if (bpf::isAtLeastKernelVersion(4, 14, 0)) {
- RETURN_IF_NOT_OK(
- attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_INET_CREATE_PROG_PATH,
+ cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+ }
+
+ if (modules::sdklevel::IsAtLeastV()) {
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_CONNECT4_PROG_PATH,
+ cg_fd, BPF_CGROUP_INET4_CONNECT));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_CONNECT6_PROG_PATH,
+ cg_fd, BPF_CGROUP_INET6_CONNECT));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP4_RECVMSG_PROG_PATH,
+ cg_fd, BPF_CGROUP_UDP4_RECVMSG));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP6_RECVMSG_PROG_PATH,
+ cg_fd, BPF_CGROUP_UDP6_RECVMSG));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP4_SENDMSG_PROG_PATH,
+ cg_fd, BPF_CGROUP_UDP4_SENDMSG));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_UDP6_SENDMSG_PROG_PATH,
+ cg_fd, BPF_CGROUP_UDP6_SENDMSG));
+
+ if (bpf::isAtLeastKernelVersion(5, 4, 0)) {
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_GETSOCKOPT_PROG_PATH,
+ cg_fd, BPF_CGROUP_GETSOCKOPT));
+ 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)) {
@@ -155,6 +158,24 @@
if (bpf::queryProgram(cg_fd, BPF_CGROUP_INET6_BIND) <= 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();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP4_RECVMSG) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP6_RECVMSG) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP4_SENDMSG) <= 0) abort();
+ if (bpf::queryProgram(cg_fd, BPF_CGROUP_UDP6_SENDMSG) <= 0) abort();
+
+ if (bpf::isAtLeastKernelVersion(5, 4, 0)) {
+ 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;
}
@@ -165,39 +186,50 @@
BpfHandler::BpfHandler(uint32_t perUidLimit, uint32_t totalLimit)
: mPerUidStatsEntriesLimit(perUidLimit), mTotalUidStatsEntriesLimit(totalLimit) {}
+static bool mainlineNetBpfLoadDone() {
+ return !access("/sys/fs/bpf/netd_shared/mainline_done", F_OK);
+}
+
// copied with minor changes from waitForProgsLoaded()
// p/m/C's staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h
static inline void waitForNetProgsLoaded() {
// infinite loop until success with 5/10/20/40/60/60/60... delay
for (int delay = 5;; delay *= 2) {
if (delay > 60) delay = 60;
- if (base::WaitForProperty("init.svc.bpfloader", "stopped", std::chrono::seconds(delay))
- && !access("/sys/fs/bpf/netd_shared", F_OK))
+ if (WaitForProperty("init.svc.mdnsd_netbpfload", "stopped", std::chrono::seconds(delay))
+ && mainlineNetBpfLoadDone())
return;
- ALOGW("Waited %ds for init.svc.bpfloader=stopped, still waiting...", delay);
+ ALOGW("Waited %ds for init.svc.mdnsd_netbpfload=stopped, still waiting...", delay);
}
}
Status BpfHandler::init(const char* cg2_path) {
+ // 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
+
if (base::GetProperty("bpf.progs_loaded", "") != "1") {
- // Make sure BPF programs are loaded before doing anything
- ALOGI("Waiting for BPF programs");
-
- // TODO: use !modules::sdklevel::IsAtLeastV() once api finalized
- if (android_get_device_api_level() < __ANDROID_API_V__) {
- waitForNetProgsLoaded();
- ALOGI("Networking BPF programs are loaded");
-
- if (!base::SetProperty("ctl.start", "mdnsd_loadbpf")) {
- ALOGE("Failed to set property ctl.start=mdnsd_loadbpf, see dmesg for reason.");
- abort();
- }
-
- ALOGI("Waiting for remaining BPF programs");
- }
-
+ // AOSP platform netd & mainline don't need this (at least prior to U QPR3),
+ // but there could be platform provided (xt_)bpf programs that oem/vendor
+ // modified netd (which calls us during init) depends on...
+ ALOGI("Waiting for platform BPF programs");
android::bpf::waitForProgsLoaded();
}
+
+ if (!mainlineNetBpfLoadDone()) {
+ // We're on < U QPR3 & it's the first time netd is starting up (unless crashlooping)
+ //
+ // On U QPR3+ netbpfload is guaranteed to run before the platform bpfloader,
+ // so waitForProgsLoaded() implies mainlineNetBpfLoadDone().
+ if (!base::SetProperty("ctl.start", "mdnsd_netbpfload")) {
+ ALOGE("Failed to set property ctl.start=mdnsd_netbpfload, see dmesg for reason.");
+ abort();
+ }
+
+ ALOGI("Waiting for Networking BPF programs");
+ waitForNetProgsLoaded();
+ ALOGI("Networking BPF programs are loaded");
+ }
+
ALOGI("BPF programs are loaded");
RETURN_IF_NOT_OK(initPrograms(cg2_path));
@@ -206,7 +238,30 @@
return netdutils::status::ok;
}
+static void mapLockTest(void) {
+ // The maps must be R/W, and as yet unopened (or more specifically not yet lock'ed).
+ const char * const m1 = BPF_NETD_PATH "map_netd_lock_array_test_map";
+ const char * const m2 = BPF_NETD_PATH "map_netd_lock_hash_test_map";
+
+ unique_fd fd0(bpf::mapRetrieveExclusiveRW(m1)); if (!fd0.ok()) abort(); // grabs exclusive lock
+
+ unique_fd fd1(bpf::mapRetrieveExclusiveRW(m2)); if (!fd1.ok()) abort(); // no conflict with fd0
+ unique_fd fd2(bpf::mapRetrieveExclusiveRW(m2)); if ( fd2.ok()) abort(); // busy due to fd1
+ unique_fd fd3(bpf::mapRetrieveRO(m2)); if (!fd3.ok()) abort(); // no lock taken
+ unique_fd fd4(bpf::mapRetrieveRW(m2)); if ( fd4.ok()) abort(); // busy due to fd1
+ fd1.reset(); // releases exclusive lock
+ unique_fd fd5(bpf::mapRetrieveRO(m2)); if (!fd5.ok()) abort(); // no lock taken
+ unique_fd fd6(bpf::mapRetrieveRW(m2)); if (!fd6.ok()) abort(); // now ok
+ unique_fd fd7(bpf::mapRetrieveRO(m2)); if (!fd7.ok()) abort(); // no lock taken
+ unique_fd fd8(bpf::mapRetrieveExclusiveRW(m2)); if ( fd8.ok()) abort(); // busy due to fd6
+
+ fd0.reset(); // releases exclusive lock
+ unique_fd fd9(bpf::mapRetrieveWO(m1)); if (!fd9.ok()) abort(); // grabs exclusive lock
+}
+
Status BpfHandler::initMaps() {
+ mapLockTest();
+
RETURN_IF_NOT_OK(mStatsMapA.init(STATS_MAP_A_PATH));
RETURN_IF_NOT_OK(mStatsMapB.init(STATS_MAP_B_PATH));
RETURN_IF_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH));
diff --git a/remoteauth/framework-udc-compat/Android.bp b/remoteauth/framework-udc-compat/Android.bp
deleted file mode 100644
index 799ffd0..0000000
--- a/remoteauth/framework-udc-compat/Android.bp
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2023 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 {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-// Sources included in the framework-connectivity jar for compatibility
-// builds in udc branches. They are only compatibility stubs to make
-// the module build, since remoteauth is not available on U.
-filegroup {
- name: "framework-remoteauth-java-sources-udc-compat",
- srcs: [
- "java/**/*.java",
- ],
- path: "java",
- visibility: [
- "//packages/modules/Connectivity/framework-t:__subpackages__",
- ],
-}
-
diff --git a/remoteauth/framework-udc-compat/java/README.md b/remoteauth/framework-udc-compat/java/README.md
deleted file mode 100644
index 7a01308..0000000
--- a/remoteauth/framework-udc-compat/java/README.md
+++ /dev/null
@@ -1,4 +0,0 @@
-# RemoteAuth udc compatibility framework files
-
-This directory is created to contain compatibility implementations of RemoteAuth classes for builds
-in udc branches.
diff --git a/remoteauth/service-udc-compat/Android.bp b/remoteauth/service-udc-compat/Android.bp
deleted file mode 100644
index 69c667d..0000000
--- a/remoteauth/service-udc-compat/Android.bp
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (C) 2023 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 {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-// Compatibility library included in the service-connectivity jar for
-// builds in udc branches. It only contains compatibility stubs to make
-// the module build, since remoteauth is not available on U.
-
-// Main lib for remoteauth services.
-java_library {
- name: "service-remoteauth-pre-jarjar-udc-compat",
- srcs: ["java/**/*.java"],
-
- defaults: [
- "framework-system-server-module-defaults"
- ],
- libs: [
- "androidx.annotation_annotation",
- "error_prone_annotations",
- ],
- sdk_version: "system_server_current",
- // This is included in service-connectivity which is 30+
- min_sdk_version: "30",
-
- dex_preopt: {
- enabled: false,
- app_image: false,
- },
- visibility: [
- "//packages/modules/Connectivity/service",
- "//packages/modules/Connectivity/service-t",
- ],
- apex_available: [
- "com.android.tethering",
- ],
-}
-
diff --git a/remoteauth/service-udc-compat/java/com/android/server/remoteauth/RemoteAuthService.java b/remoteauth/service-udc-compat/java/com/android/server/remoteauth/RemoteAuthService.java
deleted file mode 100644
index ac4fde1..0000000
--- a/remoteauth/service-udc-compat/java/com/android/server/remoteauth/RemoteAuthService.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.remoteauth;
-
-import android.os.Binder;
-import android.content.Context;
-
-/** Compatibility stub for RemoteAuthService in udc branch builds. */
-public class RemoteAuthService extends Binder {
- public static final String SERVICE_NAME = "remote_auth";
- public RemoteAuthService(Context context) {
- throw new UnsupportedOperationException("RemoteAuthService is not supported in this build");
- }
-}
diff --git a/remoteauth/service/jni/Android.bp b/remoteauth/service/jni/Android.bp
index c0ac779..fc91e0c 100644
--- a/remoteauth/service/jni/Android.bp
+++ b/remoteauth/service/jni/Android.bp
@@ -12,7 +12,7 @@
srcs: ["src/lib.rs"],
rustlibs: [
"libbinder_rs",
- "libjni",
+ "libjni_legacy",
"liblazy_static",
"liblog_rust",
"liblogger",
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 012c076..96efd18 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -20,7 +20,7 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-service_remoteauth_pre_jarjar_lib = "service-remoteauth-pre-jarjar-udc-compat"
+service_remoteauth_pre_jarjar_lib = "service-remoteauth-pre-jarjar"
// Include build rules from Sources.bp
build = ["Sources.bp"]
@@ -100,7 +100,7 @@
min_sdk_version: "21",
lint: {
error_checks: ["NewApi"],
-
+ baseline_filename: "lint-baseline-service-connectivity-mdns-standalone-build-test.xml",
},
srcs: [
"src/com/android/server/connectivity/mdns/**/*.java",
diff --git a/service-t/lint-baseline-service-connectivity-mdns-standalone-build-test.xml b/service-t/lint-baseline-service-connectivity-mdns-standalone-build-test.xml
new file mode 100644
index 0000000..232d31c
--- /dev/null
+++ b/service-t/lint-baseline-service-connectivity-mdns-standalone-build-test.xml
@@ -0,0 +1,972 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadEngine.java"
+ line="37"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="@FlaggedApi should specify an actual flag constant; raw strings are discouraged (and more importantly, **not enforced**)"
+ errorLine1="@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/framework-t/src/android/net/nsd/OffloadServiceInfo.java"
+ line="43"
+ column="13"/>
+ </issue>
+
+</issues>
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index c620634..b1925bd 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -76,6 +76,8 @@
"-Wno-unused-parameter",
"-Wthread-safety",
],
+ version_script: ":connectivity_mainline_test_map",
+ stl: "libc++_static",
static_libs: [
"libbase",
"libgmock",
diff --git a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
index 385adc6..a92dfaf 100644
--- a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
+++ b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
@@ -193,7 +193,8 @@
* @param sentQueryCount The count of sent queries before stop discovery.
*/
public void reportServiceDiscoveryStop(boolean isLegacy, int transactionId, long durationMs,
- int foundCallbackCount, int lostCallbackCount, int servicesCount, int sentQueryCount) {
+ int foundCallbackCount, int lostCallbackCount, int servicesCount, int sentQueryCount,
+ boolean isServiceFromCache) {
final Builder builder = makeReportedBuilder(isLegacy, transactionId);
builder.setType(NsdEventType.NET_DISCOVER);
builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_DISCOVERY_STOP);
@@ -202,6 +203,7 @@
builder.setLostCallbackCount(lostCallbackCount);
builder.setFoundServiceCount(servicesCount);
builder.setSentQueryCount(sentQueryCount);
+ builder.setIsKnownService(isServiceFromCache);
mDependencies.statsWrite(builder.build());
}
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 0a8adf0..64624ae 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1634,6 +1634,12 @@
NsdManager.nameOf(code), transactionId));
switch (code) {
case NsdManager.SERVICE_FOUND:
+ // Set the ServiceFromCache flag only if the service is actually being
+ // retrieved from the cache. This flag should not be overridden by later
+ // service found event, which may not be cached.
+ if (event.mIsServiceFromCache) {
+ request.setServiceFromCache(true);
+ }
clientInfo.onServiceFound(clientRequestId, info, request);
break;
case NsdManager.SERVICE_LOST:
@@ -1917,13 +1923,13 @@
mContext, MdnsFeatureFlags.NSD_FORCE_DISABLE_MDNS_OFFLOAD))
.setIncludeInetAddressRecordsInProbing(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.INCLUDE_INET_ADDRESS_RECORDS_IN_PROBING))
- .setIsExpiredServicesRemovalEnabled(mDeps.isFeatureEnabled(
+ .setIsExpiredServicesRemovalEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_EXPIRED_SERVICES_REMOVAL))
.setIsLabelCountLimitEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_LIMIT_LABEL_COUNT))
- .setIsKnownAnswerSuppressionEnabled(mDeps.isFeatureEnabled(
+ .setIsKnownAnswerSuppressionEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_KNOWN_ANSWER_SUPPRESSION))
- .setIsUnicastReplyEnabled(mDeps.isFeatureEnabled(
+ .setIsUnicastReplyEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_UNICAST_REPLY_ENABLED))
.setIsAggressiveQueryModeEnabled(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.NSD_AGGRESSIVE_QUERY_MODE))
@@ -2887,7 +2893,8 @@
request.getFoundServiceCount(),
request.getLostServiceCount(),
request.getServicesCount(),
- request.getSentQueryCount());
+ request.getSentQueryCount(),
+ request.isServiceFromCache());
} else if (listener instanceof ResolutionListener) {
mMetrics.reportServiceResolutionStop(false /* isLegacy */, transactionId,
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
@@ -2927,7 +2934,8 @@
request.getFoundServiceCount(),
request.getLostServiceCount(),
request.getServicesCount(),
- NO_SENT_QUERY_COUNT);
+ NO_SENT_QUERY_COUNT,
+ request.isServiceFromCache());
break;
case NsdManager.RESOLVE_SERVICE:
stopResolveService(transactionId);
@@ -3050,7 +3058,8 @@
request.getFoundServiceCount(),
request.getLostServiceCount(),
request.getServicesCount(),
- request.getSentQueryCount());
+ request.getSentQueryCount(),
+ request.isServiceFromCache());
try {
mCb.onStopDiscoverySucceeded(listenerKey);
} catch (RemoteException e) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index 42efcac..b870477 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -449,7 +449,7 @@
* Get the ID of a conflicting registration due to host, or -1 if none.
*
* <p>If there's already another registration with the same hostname requested by another
- * user, this is a conflict.
+ * UID, this is a conflict.
*
* <p>If there're two registrations both containing address records using the same hostname,
* this is a conflict.
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
index f4a08ba..c264f25 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -189,10 +189,10 @@
public Builder() {
mIsMdnsOffloadFeatureEnabled = false;
mIncludeInetAddressRecordsInProbing = false;
- mIsExpiredServicesRemovalEnabled = false;
+ mIsExpiredServicesRemovalEnabled = true; // Default enabled.
mIsLabelCountLimitEnabled = true; // Default enabled.
- mIsKnownAnswerSuppressionEnabled = false;
- mIsUnicastReplyEnabled = true;
+ mIsKnownAnswerSuppressionEnabled = true; // Default enabled.
+ mIsUnicastReplyEnabled = true; // Default enabled.
mIsAggressiveQueryModeEnabled = false;
mIsQueryWithKnownAnswerEnabled = false;
mOverrideProvider = null;
diff --git a/service-t/src/com/android/server/ethernet/EthernetInterfaceStateMachine.java b/service-t/src/com/android/server/ethernet/EthernetInterfaceStateMachine.java
new file mode 100644
index 0000000..b8f6859
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetInterfaceStateMachine.java
@@ -0,0 +1,344 @@
+/*
+ * 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.server.ethernet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkProvider.NetworkOfferCallback;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.net.ip.IIpClient;
+import android.net.ip.IpClientCallbacks;
+import android.net.ip.IpClientManager;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Message;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.util.State;
+import com.android.net.module.util.SyncStateMachine;
+import com.android.net.module.util.SyncStateMachine.StateInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * EthernetInterfaceStateMachine manages the lifecycle of an ethernet-like network interface which
+ * includes managing a NetworkOffer, IpClient, and NetworkAgent as well as making the interface
+ * available as a tethering downstream.
+ *
+ * All methods exposed by this class *must* be called on the Handler thread provided in the
+ * constructor.
+ */
+class EthernetInterfaceStateMachine extends SyncStateMachine {
+ private static final String TAG = EthernetInterfaceStateMachine.class.getSimpleName();
+
+ private static final int CMD_ON_LINK_UP = 1;
+ private static final int CMD_ON_LINK_DOWN = 2;
+ private static final int CMD_ON_NETWORK_NEEDED = 3;
+ private static final int CMD_ON_NETWORK_UNNEEDED = 4;
+ private static final int CMD_ON_IPCLIENT_CREATED = 5;
+
+ private class EthernetNetworkOfferCallback implements NetworkOfferCallback {
+ private final Set<Integer> mRequestIds = new ArraySet<>();
+
+ @Override
+ public void onNetworkNeeded(@NonNull NetworkRequest request) {
+ if (this != mNetworkOfferCallback) {
+ return;
+ }
+
+ mRequestIds.add(request.requestId);
+ if (mRequestIds.size() == 1) {
+ processMessage(CMD_ON_NETWORK_NEEDED);
+ }
+ }
+
+ @Override
+ public void onNetworkUnneeded(@NonNull NetworkRequest request) {
+ if (this != mNetworkOfferCallback) {
+ return;
+ }
+
+ if (!mRequestIds.remove(request.requestId)) {
+ // This can only happen if onNetworkNeeded was not called for a request or if
+ // the requestId changed. Both should *never* happen.
+ Log.wtf(TAG, "onNetworkUnneeded called for unknown request");
+ }
+ if (mRequestIds.isEmpty()) {
+ processMessage(CMD_ON_NETWORK_UNNEEDED);
+ }
+ }
+ }
+
+ private class EthernetIpClientCallback extends IpClientCallbacks {
+ private final ConditionVariable mOnQuitCv = new ConditionVariable(false);
+
+ private void safelyPostOnHandler(Runnable r) {
+ mHandler.post(() -> {
+ if (this != mIpClientCallback) {
+ return;
+ }
+ r.run();
+ });
+ }
+
+ @Override
+ public void onIpClientCreated(IIpClient ipClient) {
+ safelyPostOnHandler(() -> {
+ // TODO: add a SyncStateMachine#processMessage(cmd, obj) overload.
+ processMessage(CMD_ON_IPCLIENT_CREATED, 0, 0,
+ mDependencies.makeIpClientManager(ipClient));
+ });
+ }
+
+ public void waitOnQuit() {
+ if (!mOnQuitCv.block(5_000 /* timeoutMs */)) {
+ Log.wtf(TAG, "Timed out waiting on IpClient to shutdown.");
+ }
+ }
+
+ @Override
+ public void onQuit() {
+ mOnQuitCv.open();
+ }
+ }
+
+ private @Nullable EthernetNetworkOfferCallback mNetworkOfferCallback;
+ private @Nullable EthernetIpClientCallback mIpClientCallback;
+ private @Nullable IpClientManager mIpClient;
+ private final String mIface;
+ private final Handler mHandler;
+ private final Context mContext;
+ private final NetworkCapabilities mCapabilities;
+ private final NetworkProvider mNetworkProvider;
+ private final EthernetNetworkFactory.Dependencies mDependencies;
+ private boolean mLinkUp = false;
+
+ /** Interface is in tethering mode. */
+ private class TetheringState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_LINK_UP:
+ case CMD_ON_LINK_DOWN:
+ // TODO: think about what to do here.
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /** Link is down */
+ private class LinkDownState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_LINK_UP:
+ transitionTo(mStoppedState);
+ return HANDLED;
+ case CMD_ON_LINK_DOWN:
+ // do nothing, already in the correct state.
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /** Parent states of all states that do not cause a NetworkOffer to be extended. */
+ private class NetworkOfferExtendedState extends State {
+ @Override
+ public void enter() {
+ if (mNetworkOfferCallback != null) {
+ // This should never happen. If it happens anyway, log and move on.
+ Log.wtf(TAG, "Previous NetworkOffer was never retracted");
+ }
+
+ mNetworkOfferCallback = new EthernetNetworkOfferCallback();
+ final NetworkScore defaultScore = new NetworkScore.Builder().build();
+ mNetworkProvider.registerNetworkOffer(defaultScore,
+ new NetworkCapabilities(mCapabilities), cmd -> mHandler.post(cmd),
+ mNetworkOfferCallback);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_LINK_UP:
+ // do nothing, already in the correct state.
+ return HANDLED;
+ case CMD_ON_LINK_DOWN:
+ transitionTo(mLinkDownState);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ mNetworkProvider.unregisterNetworkOffer(mNetworkOfferCallback);
+ mNetworkOfferCallback = null;
+ }
+ }
+
+ /**
+ * Offer is extended but has not been requested.
+ *
+ * StoppedState's sole purpose is to react to a CMD_ON_NETWORK_NEEDED and transition to
+ * StartedState when that happens. Note that StoppedState could be rolled into
+ * NetworkOfferExtendedState. However, keeping the states separate provides some additional
+ * protection by logging a Log.wtf if a CMD_ON_NETWORK_NEEDED is received in an unexpected state
+ * (i.e. StartedState or RunningState). StoppedState is a child of NetworkOfferExtendedState.
+ */
+ private class StoppedState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_NETWORK_NEEDED:
+ transitionTo(mStartedState);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /** Network is needed, starts IpClient and manages its lifecycle */
+ private class StartedState extends State {
+ @Override
+ public void enter() {
+ mIpClientCallback = new EthernetIpClientCallback();
+ mDependencies.makeIpClient(mContext, mIface, mIpClientCallback);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ON_NETWORK_UNNEEDED:
+ transitionTo(mStoppedState);
+ return HANDLED;
+ case CMD_ON_IPCLIENT_CREATED:
+ mIpClient = (IpClientManager) msg.obj;
+ transitionTo(mRunningState);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ if (mIpClient != null) {
+ mIpClient.shutdown();
+ // TODO: consider adding a StoppingState and making the shutdown operation
+ // asynchronous.
+ mIpClientCallback.waitOnQuit();
+ }
+ mIpClientCallback = null;
+ }
+ }
+
+ /** IpClient is running, starts provisioning and registers NetworkAgent */
+ private class RunningState extends State {
+
+ }
+
+ private final TetheringState mTetheringState = new TetheringState();
+ private final LinkDownState mLinkDownState = new LinkDownState();
+ private final NetworkOfferExtendedState mOfferExtendedState = new NetworkOfferExtendedState();
+ private final StoppedState mStoppedState = new StoppedState();
+ private final StartedState mStartedState = new StartedState();
+ private final RunningState mRunningState = new RunningState();
+
+ public EthernetInterfaceStateMachine(String iface, Handler handler, Context context,
+ NetworkCapabilities capabilities, NetworkProvider provider,
+ EthernetNetworkFactory.Dependencies deps) {
+ super(TAG + "." + iface, handler.getLooper().getThread());
+
+ mIface = iface;
+ mHandler = handler;
+ mContext = context;
+ mCapabilities = capabilities;
+ mNetworkProvider = provider;
+ mDependencies = deps;
+
+ // Interface lifecycle:
+ // [ LinkDownState ]
+ // |
+ // v
+ // *link comes up*
+ // |
+ // v
+ // [ StoppedState ]
+ // |
+ // v
+ // *network is needed*
+ // |
+ // v
+ // [ StartedState ]
+ // |
+ // v
+ // *IpClient is created*
+ // |
+ // v
+ // [ RunningState ]
+ // |
+ // v
+ // *interface is requested for tethering*
+ // |
+ // v
+ // [TetheringState]
+ //
+ // Tethering mode is special as the interface is configured by Tethering, rather than the
+ // ethernet module.
+ final List<StateInfo> states = new ArrayList<>();
+ states.add(new StateInfo(mTetheringState, null));
+
+ // CHECKSTYLE:OFF IndentationCheck
+ // Initial state
+ states.add(new StateInfo(mLinkDownState, null));
+ states.add(new StateInfo(mOfferExtendedState, null));
+ states.add(new StateInfo(mStoppedState, mOfferExtendedState));
+ states.add(new StateInfo(mStartedState, mOfferExtendedState));
+ states.add(new StateInfo(mRunningState, mStartedState));
+ // CHECKSTYLE:ON IndentationCheck
+ addAllStates(states);
+
+ // TODO: set initial state to TetheringState if a tethering interface has been requested and
+ // this is the first interface to be added.
+ start(mLinkDownState);
+ }
+
+ public boolean updateLinkState(boolean up) {
+ if (mLinkUp == up) {
+ return false;
+ }
+
+ // TODO: consider setting mLinkUp as part of processMessage().
+ mLinkUp = up;
+ if (!up) { // was up, goes down
+ processMessage(CMD_ON_LINK_DOWN);
+ } else { // was down, comes up
+ processMessage(CMD_ON_LINK_UP);
+ }
+
+ return true;
+ }
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 034353c..114cf2e 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -57,6 +57,7 @@
import static android.net.TrafficStats.TYPE_TX_PACKETS;
import static android.net.TrafficStats.UID_TETHERING;
import static android.net.TrafficStats.UNSUPPORTED;
+import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
@@ -91,6 +92,7 @@
import android.app.AlarmManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
import android.app.usage.NetworkStatsManager;
import android.content.ApexEnvironment;
import android.content.BroadcastReceiver;
@@ -472,7 +474,9 @@
private final TrafficStatsRateLimitCache mTrafficStatsUidCache;
static final String TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG =
"trafficstats_rate_limit_cache_enabled_flag";
- private final boolean mSupportTrafficStatsRateLimitCache;
+ private final boolean mAlwaysUseTrafficStatsRateLimitCache;
+ private final int mTrafficStatsRateLimitCacheExpiryDuration;
+ private final int mTrafficStatsRateLimitCacheMaxEntries;
private final Object mOpenSessionCallsLock = new Object();
@@ -663,15 +667,18 @@
mEventLogger = null;
}
- final long cacheExpiryDurationMs = mDeps.getTrafficStatsRateLimitCacheExpiryDuration();
- final int cacheMaxEntries = mDeps.getTrafficStatsRateLimitCacheMaxEntries();
- mSupportTrafficStatsRateLimitCache = mDeps.supportTrafficStatsRateLimitCache(mContext);
+ mAlwaysUseTrafficStatsRateLimitCache =
+ mDeps.alwaysUseTrafficStatsRateLimitCache(mContext);
+ mTrafficStatsRateLimitCacheExpiryDuration =
+ mDeps.getTrafficStatsRateLimitCacheExpiryDuration();
+ mTrafficStatsRateLimitCacheMaxEntries =
+ mDeps.getTrafficStatsRateLimitCacheMaxEntries();
mTrafficStatsTotalCache = new TrafficStatsRateLimitCache(mClock,
- cacheExpiryDurationMs, cacheMaxEntries);
+ mTrafficStatsRateLimitCacheExpiryDuration, mTrafficStatsRateLimitCacheMaxEntries);
mTrafficStatsIfaceCache = new TrafficStatsRateLimitCache(mClock,
- cacheExpiryDurationMs, cacheMaxEntries);
+ mTrafficStatsRateLimitCacheExpiryDuration, mTrafficStatsRateLimitCacheMaxEntries);
mTrafficStatsUidCache = new TrafficStatsRateLimitCache(mClock,
- cacheExpiryDurationMs, cacheMaxEntries);
+ mTrafficStatsRateLimitCacheExpiryDuration, mTrafficStatsRateLimitCacheMaxEntries);
// TODO: Remove bpfNetMaps creation and always start SkDestroyListener
// Following code is for the experiment to verify the SkDestroyListener refactoring. Based
@@ -920,13 +927,14 @@
}
/**
- * Get whether TrafficStats rate-limit cache is supported.
+ * Get whether TrafficStats rate-limit cache is always applied.
*
* This method should only be called once in the constructor,
* to ensure that the code does not need to deal with flag values changing at runtime.
*/
- public boolean supportTrafficStatsRateLimitCache(@NonNull Context ctx) {
- return false;
+ public boolean alwaysUseTrafficStatsRateLimitCache(@NonNull Context ctx) {
+ return SdkLevel.isAtLeastV() && DeviceConfigUtils.isTetheringFeatureNotChickenedOut(
+ ctx, TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG);
}
/**
@@ -954,6 +962,13 @@
}
/**
+ * Wrapper method for {@link CompatChanges#isChangeEnabled(long, int)}
+ */
+ public boolean isChangeEnabled(final long changeId, final int uid) {
+ return CompatChanges.isChangeEnabled(changeId, uid);
+ }
+
+ /**
* Retrieves native network total statistics.
*
* @return A NetworkStats.Entry containing the native statistics, or
@@ -2089,14 +2104,14 @@
}
if (!isEntryValueTypeValid(type)) return UNSUPPORTED;
- if (!mSupportTrafficStatsRateLimitCache) {
- return getEntryValueForType(mDeps.nativeGetUidStat(uid), type);
+ if (mAlwaysUseTrafficStatsRateLimitCache
+ || mDeps.isChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, callingUid)) {
+ final NetworkStats.Entry entry = mTrafficStatsUidCache.getOrCompute(IFACE_ALL, uid,
+ () -> mDeps.nativeGetUidStat(uid));
+ return getEntryValueForType(entry, type);
}
- final NetworkStats.Entry entry = mTrafficStatsUidCache.getOrCompute(IFACE_ALL, uid,
- () -> mDeps.nativeGetUidStat(uid));
-
- return getEntryValueForType(entry, type);
+ return getEntryValueForType(mDeps.nativeGetUidStat(uid), type);
}
@Nullable
@@ -2118,14 +2133,15 @@
Objects.requireNonNull(iface);
if (!isEntryValueTypeValid(type)) return UNSUPPORTED;
- if (!mSupportTrafficStatsRateLimitCache) {
- return getEntryValueForType(getIfaceStatsInternal(iface), type);
+ if (mAlwaysUseTrafficStatsRateLimitCache
+ || mDeps.isChangeEnabled(
+ ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, Binder.getCallingUid())) {
+ final NetworkStats.Entry entry = mTrafficStatsIfaceCache.getOrCompute(iface, UID_ALL,
+ () -> getIfaceStatsInternal(iface));
+ return getEntryValueForType(entry, type);
}
- final NetworkStats.Entry entry = mTrafficStatsIfaceCache.getOrCompute(iface, UID_ALL,
- () -> getIfaceStatsInternal(iface));
-
- return getEntryValueForType(entry, type);
+ return getEntryValueForType(getIfaceStatsInternal(iface), type);
}
private long getEntryValueForType(@Nullable NetworkStats.Entry entry, int type) {
@@ -2171,14 +2187,15 @@
@Override
public long getTotalStats(int type) {
if (!isEntryValueTypeValid(type)) return UNSUPPORTED;
- if (!mSupportTrafficStatsRateLimitCache) {
- return getEntryValueForType(getTotalStatsInternal(), type);
+ if (mAlwaysUseTrafficStatsRateLimitCache
+ || mDeps.isChangeEnabled(
+ ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, Binder.getCallingUid())) {
+ final NetworkStats.Entry entry = mTrafficStatsTotalCache.getOrCompute(
+ IFACE_ALL, UID_ALL, () -> getTotalStatsInternal());
+ return getEntryValueForType(entry, type);
}
- final NetworkStats.Entry entry = mTrafficStatsTotalCache.getOrCompute(IFACE_ALL, UID_ALL,
- () -> getTotalStatsInternal());
-
- return getEntryValueForType(entry, type);
+ return getEntryValueForType(getTotalStatsInternal(), type);
}
@Override
@@ -2942,13 +2959,12 @@
} catch (IOException e) {
pw.println("(failed to dump FastDataInput counters)");
}
- pw.print("trafficstats.cache.supported", mSupportTrafficStatsRateLimitCache);
+ pw.print("trafficstats.cache.alwaysuse", mAlwaysUseTrafficStatsRateLimitCache);
pw.println();
pw.print(TRAFFIC_STATS_CACHE_EXPIRY_DURATION_NAME,
- mDeps.getTrafficStatsRateLimitCacheExpiryDuration());
+ mTrafficStatsRateLimitCacheExpiryDuration);
pw.println();
- pw.print(TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME,
- mDeps.getTrafficStatsRateLimitCacheMaxEntries());
+ pw.print(TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME, mTrafficStatsRateLimitCacheMaxEntries);
pw.println();
pw.decreaseIndent();
diff --git a/service/Android.bp b/service/Android.bp
index 7c22ca5..1dd09a9 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -20,7 +20,7 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-service_remoteauth_pre_jarjar_lib = "service-remoteauth-pre-jarjar-udc-compat"
+service_remoteauth_pre_jarjar_lib = "service-remoteauth-pre-jarjar"
// The above variables may have different values
// depending on the branch, and this comment helps
@@ -188,7 +188,7 @@
"androidx.annotation_annotation",
"connectivity-net-module-utils-bpf",
"connectivity_native_aidl_interface-lateststable-java",
- "dnsresolver_aidl_interface-V14-java",
+ "dnsresolver_aidl_interface-V15-java",
"modules-utils-shell-command-handler",
"net-utils-device-common",
"net-utils-device-common-ip",
diff --git a/service/ServiceConnectivityResources/res/values-fa/strings.xml b/service/ServiceConnectivityResources/res/values-fa/strings.xml
index 02c60df..09f1255 100644
--- a/service/ServiceConnectivityResources/res/values-fa/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-fa/strings.xml
@@ -23,15 +23,15 @@
<!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
<string name="mobile_network_available_no_internet" msgid="1000871587359324217">"اتصال اینترنت وجود ندارد"</string>
- <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"ممکن است داده <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> تمام شده باشد. برای گزینهها ضربه بزنید."</string>
- <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"ممکن است داده شما تمام شده باشد. برای گزینهها ضربه بزنید."</string>
+ <string name="mobile_network_available_no_internet_detailed" msgid="5438738723127062816">"ممکن است داده <xliff:g id="NETWORK_CARRIER">%1$s</xliff:g> تمام شده باشد. برای گزینهها تکضرب بزنید."</string>
+ <string name="mobile_network_available_no_internet_detailed_unknown_carrier" msgid="5375681117265354337">"ممکن است داده شما تمام شده باشد. برای گزینهها تکضرب بزنید."</string>
<string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> به اینترنت دسترسی ندارد"</string>
- <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"برای گزینهها ضربه بزنید"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"برای گزینهها تکضرب بزنید"</string>
<string name="mobile_no_internet" msgid="4087718456753201450">"شبکه تلفن همراه به اینترنت دسترسی ندارد"</string>
<string name="other_networks_no_internet" msgid="5693932964749676542">"شبکه به اینترنت دسترسی ندارد"</string>
<string name="private_dns_broken_detailed" msgid="2677123850463207823">"سرور DNS خصوصی قابل دسترسی نیست"</string>
<string name="network_partial_connectivity" msgid="5549503845834993258">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> اتصال محدودی دارد"</string>
- <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"بههرصورت، برای اتصال ضربه بزنید"</string>
+ <string name="network_partial_connectivity_detailed" msgid="4732435946300249845">"بههرصورت، برای اتصال تکضرب بزنید"</string>
<string name="network_switch_metered" msgid="5016937523571166319">"به <xliff:g id="NETWORK_TYPE">%1$s</xliff:g> تغییر کرد"</string>
<string name="network_switch_metered_detail" msgid="1257300152739542096">"وقتی <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> به اینترنت دسترسی نداشته باشد، دستگاه از <xliff:g id="NEW_NETWORK">%1$s</xliff:g> استفاده میکند. ممکن است هزینههایی اعمال شود."</string>
<string name="network_switch_metered_toast" msgid="70691146054130335">"از <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> به <xliff:g id="NEW_NETWORK">%2$s</xliff:g> تغییر کرد"</string>
diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml
index 4783f2b..02a9ce6 100644
--- a/service/ServiceConnectivityResources/res/values/config_thread.xml
+++ b/service/ServiceConnectivityResources/res/values/config_thread.xml
@@ -53,4 +53,10 @@
UTF-8 bytes.
-->
<string translatable="false" name="config_thread_model_name">Thread Border Router</string>
+
+ <!-- Whether the Thread network will be managed by the Google Home ecosystem. When this value
+ is set, a TXT entry "vgh=0" or "vgh=1" will be added to the "_mehscop._udp" mDNS service
+ respectively (The TXT value is a string).
+ -->
+ <bool name="config_thread_managed_by_google_home">false</bool>
</resources>
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index c07d050..c0082bb 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -80,7 +80,8 @@
case VERIFY_BIN: return;
case VERIFY_PROG: fd = bpf::retrieveProgram(path); break;
case VERIFY_MAP_RO: fd = bpf::mapRetrieveRO(path); break;
- case VERIFY_MAP_RW: fd = bpf::mapRetrieveRW(path); break;
+ // lockless: we're just checking access rights and will immediately close the fd
+ case VERIFY_MAP_RW: fd = bpf::mapRetrieveLocklessRW(path); break;
}
if (fd < 0) ALOGF("bpf_obj_get '%s' failed, errno=%d", path, errno);
@@ -114,12 +115,7 @@
V("/sys/fs/bpf", S_IFDIR|S_ISVTX|0777, ROOT, ROOT, "fs_bpf", DIR);
- // TODO: use modules::sdklevel::IsAtLeastV() once api finalized
- if (android_get_device_api_level() >= __ANDROID_API_V__) {
- V("/sys/fs/bpf/net_shared", S_IFDIR|01777, ROOT, ROOT, "fs_bpf_net_shared", DIR);
- } else {
- V("/sys/fs/bpf/net_shared", S_IFDIR|01777, SYSTEM, SYSTEM, "fs_bpf_net_shared", DIR);
- }
+ V("/sys/fs/bpf/net_shared", S_IFDIR|01777, ROOT, ROOT, "fs_bpf_net_shared", DIR);
// pre-U we do not have selinux privs to getattr on bpf maps/progs
// so while the below *should* be as listed, we have no way to actually verify
diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml
index 3e11d52..b09589c 100644
--- a/service/lint-baseline.xml
+++ b/service/lint-baseline.xml
@@ -3,17 +3,6 @@
<issue
id="NewApi"
- message="Call requires API level 33 (current min is 30): `getUidRule`"
- errorLine1=" return BpfNetMapsReader.getUidRule(sUidOwnerMap, childChain, uid);"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/service/src/com/android/server/BpfNetMaps.java"
- line="643"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
message="Call requires API level 31 (current min is 30): `BpfBitmap`"
errorLine1=" return new BpfBitmap(BLOCKED_PORTS_MAP_PATH);"
errorLine2=" ~~~~~~~~~~~~~">
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 23af0f8..44868b2d 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -55,6 +55,7 @@
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArraySet;
@@ -71,6 +72,7 @@
import com.android.net.module.util.BpfDump;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.SingleWriterBpfMap;
import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.Struct.U32;
@@ -187,7 +189,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U32> getConfigurationMap() {
try {
- return new BpfMap<>(
+ return SingleWriterBpfMap.getSingleton(
CONFIGURATION_MAP_PATH, S32.class, U32.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open netd configuration map", e);
@@ -197,7 +199,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
try {
- return new BpfMap<>(
+ return SingleWriterBpfMap.getSingleton(
UID_OWNER_MAP_PATH, S32.class, UidOwnerValue.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open uid owner map", e);
@@ -207,7 +209,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U8> getUidPermissionMap() {
try {
- return new BpfMap<>(
+ return SingleWriterBpfMap.getSingleton(
UID_PERMISSION_MAP_PATH, S32.class, U8.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open uid permission map", e);
@@ -217,6 +219,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
try {
+ // Cannot use SingleWriterBpfMap because it's written by ClatCoordinator as well.
return new BpfMap<>(COOKIE_TAG_MAP_PATH,
CookieTagMapKey.class, CookieTagMapValue.class);
} catch (ErrnoException e) {
@@ -227,7 +230,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U8> getDataSaverEnabledMap() {
try {
- return new BpfMap<>(
+ return SingleWriterBpfMap.getSingleton(
DATA_SAVER_ENABLED_MAP_PATH, S32.class, U8.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open data saver enabled map", e);
@@ -237,7 +240,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<IngressDiscardKey, IngressDiscardValue> getIngressDiscardMap() {
try {
- return new BpfMap<>(INGRESS_DISCARD_MAP_PATH,
+ return SingleWriterBpfMap.getSingleton(INGRESS_DISCARD_MAP_PATH,
IngressDiscardKey.class, IngressDiscardValue.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open ingress discard map", e);
@@ -577,6 +580,7 @@
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public int getUidRule(final int childChain, final int uid) {
return BpfNetMapsUtils.getUidRule(sUidOwnerMap, childChain, uid);
}
@@ -815,8 +819,11 @@
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public int getNetPermForUid(final int uid) {
+ final int appId = UserHandle.getAppId(uid);
try {
- final U8 permissions = sUidPermissionMap.getValue(new S32(uid));
+ // Key of uid permission map is appId
+ // TODO: Rename map name
+ final U8 permissions = sUidPermissionMap.getValue(new S32(appId));
return permissions != null ? permissions.val : PERMISSION_INTERNET;
} catch (ErrnoException e) {
Log.wtf(TAG, "Failed to get permission for uid: " + uid);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 39ea286..2a3058c 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -36,14 +36,17 @@
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NETWORK_RESTRICTED;
import static android.net.ConnectivityManager.CALLBACK_IP_CHANGED;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
+import static android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_NONE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
@@ -63,6 +66,7 @@
import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_ALL;
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.INetd.PERMISSION_INTERNET;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
@@ -75,6 +79,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -109,26 +114,35 @@
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_GETSOCKOPT;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_BIND;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET4_CONNECT;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_BIND;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET6_CONNECT;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_EGRESS;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_INGRESS;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_SOCK_CREATE;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_INET_SOCK_RELEASE;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_SETSOCKOPT;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP4_RECVMSG;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP4_SENDMSG;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP6_RECVMSG;
+import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP6_SENDMSG;
import static com.android.net.module.util.NetworkMonitorUtils.isPrivateDnsValidationRequired;
import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
import static com.android.net.module.util.PermissionUtils.hasAnyPermissionOf;
import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE;
+import static com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS;
import static com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI;
import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
-import static java.util.Map.Entry;
-
import android.Manifest;
import android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresApi;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.ActivityManager;
@@ -277,8 +291,6 @@
import android.util.SparseIntArray;
import android.util.StatsEvent;
-import androidx.annotation.RequiresApi;
-
import com.android.connectivity.resources.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -400,6 +412,8 @@
private static final String NETWORK_ARG = "networks";
private static final String REQUEST_ARG = "requests";
private static final String TRAFFICCONTROLLER_ARG = "trafficcontroller";
+ public static final String CLATEGRESS4RAWBPFMAP_ARG = "clatEgress4RawBpfMap";
+ public static final String CLATINGRESS6RAWBPFMAP_ARG = "clatIngress6RawBpfMap";
private static final boolean DBG = true;
private static final boolean DDBG = Log.isLoggable(TAG, Log.DEBUG);
@@ -484,9 +498,33 @@
private final boolean mRequestRestrictedWifiEnabled;
private final boolean mBackgroundFirewallChainEnabled;
+ private final boolean mUseDeclaredMethodsForCallbacksEnabled;
+
/**
- * Stale copy of uid blocked reasons provided by NPMS. As long as they are accessed only in
- * internal handler thread, they don't need a lock.
+ * Uids ConnectivityService tracks blocked status of to send blocked status callbacks.
+ * Key is uid based on mAsUid of registered networkRequestInfo
+ * Value is count of registered networkRequestInfo
+ *
+ * This is necessary because when a firewall chain is enabled or disabled, that affects all UIDs
+ * on the system, not just UIDs on that firewall chain. For example, entering doze mode affects
+ * all UIDs that are not on the dozable chain. ConnectivityService doesn't know which UIDs are
+ * running. But it only needs to send onBlockedStatusChanged to UIDs that have at least one
+ * NetworkCallback registered.
+ *
+ * UIDs are added to this list on the binder thread when processing requestNetwork and similar
+ * IPCs. They are removed from this list on the handler thread, when the callback unregistration
+ * is fully processed. They cannot be unregistered when the unregister IPC is processed because
+ * sometimes requests are unregistered on the handler thread.
+ */
+ @GuardedBy("mBlockedStatusTrackingUids")
+ private final SparseIntArray mBlockedStatusTrackingUids = new SparseIntArray();
+
+ /**
+ * Stale copy of UID blocked reasons. This is used to send onBlockedStatusChanged
+ * callbacks. This is only used on the handler thread, so it does not require a lock.
+ * On U-, the blocked reasons come from NPMS.
+ * On V+, the blocked reasons come from the BPF map contents and only maintains blocked reasons
+ * of uids that register network callbacks.
*/
private final SparseIntArray mUidBlockedReasons = new SparseIntArray();
@@ -554,6 +592,8 @@
* default network for each app.
* Order ints passed to netd must be in the 0~999 range. Larger values code for
* a lower priority, see {@link NativeUidRangeConfig}.
+ * Note that only the highest priority preference is applied if the uid is the target of
+ * multiple preferences.
*
* Requests that don't code for a per-app preference use PREFERENCE_ORDER_INVALID.
* The default request uses PREFERENCE_ORDER_DEFAULT.
@@ -788,11 +828,10 @@
private static final int EVENT_SET_PROFILE_NETWORK_PREFERENCE = 50;
/**
- * Event to specify that reasons for why an uid is blocked changed.
- * arg1 = uid
- * arg2 = blockedReasons
+ * Event to update blocked reasons for uids.
+ * obj = List of Pair(uid, blockedReasons)
*/
- private static final int EVENT_UID_BLOCKED_REASON_CHANGED = 51;
+ private static final int EVENT_BLOCKED_REASONS_CHANGED = 51;
/**
* Event to register a new network offer
@@ -853,6 +892,18 @@
private static final int EVENT_UID_FROZEN_STATE_CHANGED = 61;
/**
+ * Event to update firewall socket destroy reasons for uids.
+ * obj = List of Pair(uid, socketDestroyReasons)
+ */
+ private static final int EVENT_UPDATE_FIREWALL_DESTROY_SOCKET_REASONS = 62;
+
+ /**
+ * Event to clear firewall socket destroy reasons for all uids.
+ * arg1 = socketDestroyReason
+ */
+ private static final int EVENT_CLEAR_FIREWALL_DESTROY_SOCKET_REASONS = 63;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -996,14 +1047,19 @@
// Flag to enable the feature of closing frozen app sockets.
private final boolean mDestroyFrozenSockets;
- // Flag to optimize closing frozen app sockets by waiting for the cellular modem to wake up.
- private final boolean mDelayDestroyFrozenSockets;
+ // Flag to optimize closing app sockets by waiting for the cellular modem to wake up.
+ private final boolean mDelayDestroySockets;
// Flag to allow SysUI to receive connectivity reports for wifi picker UI.
private final boolean mAllowSysUiConnectivityReports;
// Uids that ConnectivityService is pending to close sockets of.
- private final Set<Integer> mPendingFrozenUids = new ArraySet<>();
+ // Key is uid and value is reasons of socket destroy
+ private final SparseIntArray mDestroySocketPendingUids = new SparseIntArray();
+
+ private static final int DESTROY_SOCKET_REASON_NONE = 0;
+ private static final int DESTROY_SOCKET_REASON_FROZEN = 1 << 0;
+ private static final int DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND = 1 << 1;
// Flag to drop packets to VPN addresses ingressing via non-VPN interfaces.
private final boolean mIngressToVpnAddressFiltering;
@@ -1732,7 +1788,7 @@
mDefaultRequest = new NetworkRequestInfo(
Process.myUid(), defaultInternetRequest, null,
null /* binder */, NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
- null /* attributionTags */);
+ null /* attributionTags */, DECLARED_METHODS_NONE);
mNetworkRequests.put(defaultInternetRequest, mDefaultRequest);
mDefaultNetworkRequests.add(mDefaultRequest);
mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest);
@@ -1805,6 +1861,8 @@
&& mDeps.isFeatureEnabled(context, REQUEST_RESTRICTED_WIFI);
mBackgroundFirewallChainEnabled = mDeps.isAtLeastV() && mDeps.isFeatureNotChickenedOut(
context, ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN);
+ mUseDeclaredMethodsForCallbacksEnabled = mDeps.isFeatureEnabled(context,
+ ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS);
mCarrierPrivilegeAuthenticator = mDeps.makeCarrierPrivilegeAuthenticator(
mContext, mTelephonyManager, mRequestRestrictedWifiEnabled,
this::handleUidCarrierPrivilegesLost, mHandler);
@@ -1821,7 +1879,12 @@
// To ensure uid state is synchronized with Network Policy, register for
// NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
// reading existing policy from disk.
- mPolicyManager.registerNetworkPolicyCallback(null, mPolicyCallback);
+ // If shouldTrackUidsForBlockedStatusCallbacks() is true (On V+), ConnectivityService
+ // updates blocked reasons when firewall chain and data saver status is updated based on
+ // bpf map contents instead of receiving callbacks from NPMS
+ if (!shouldTrackUidsForBlockedStatusCallbacks()) {
+ mPolicyManager.registerNetworkPolicyCallback(null, mPolicyCallback);
+ }
final PowerManager powerManager = (PowerManager) context.getSystemService(
Context.POWER_SERVICE);
@@ -1952,8 +2015,7 @@
mDestroyFrozenSockets = mDeps.isAtLeastV() || (mDeps.isAtLeastU()
&& mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION));
- mDelayDestroyFrozenSockets = mDeps.isAtLeastU()
- && mDeps.isFeatureEnabled(context, DELAY_DESTROY_FROZEN_SOCKETS_VERSION);
+ mDelayDestroySockets = mDeps.isFeatureNotChickenedOut(context, DELAY_DESTROY_SOCKETS);
mAllowSysUiConnectivityReports = mDeps.isFeatureNotChickenedOut(
mContext, ALLOW_SYSUI_CONNECTIVITY_REPORTS);
if (mDestroyFrozenSockets) {
@@ -2113,7 +2175,7 @@
handleRegisterNetworkRequest(new NetworkRequestInfo(
Process.myUid(), networkRequest, null /* messenger */, null /* binder */,
NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
- null /* attributionTags */));
+ null /* attributionTags */, DECLARED_METHODS_NONE));
} else {
handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID,
/* callOnUnavailable */ false);
@@ -3309,14 +3371,38 @@
private final NetworkPolicyCallback mPolicyCallback = new NetworkPolicyCallback() {
@Override
public void onUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_UID_BLOCKED_REASON_CHANGED,
- uid, blockedReasons));
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ Log.wtf(TAG, "Received unexpected NetworkPolicy callback");
+ return;
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_BLOCKED_REASONS_CHANGED,
+ List.of(new Pair<>(uid, blockedReasons))));
}
};
- private void handleUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {
- maybeNotifyNetworkBlockedForNewState(uid, blockedReasons);
- setUidBlockedReasons(uid, blockedReasons);
+ private boolean shouldTrackUidsForBlockedStatusCallbacks() {
+ return mDeps.isAtLeastV();
+ }
+
+ @VisibleForTesting
+ void handleBlockedReasonsChanged(List<Pair<Integer, Integer>> reasonsList) {
+ for (Pair<Integer, Integer> reasons: reasonsList) {
+ final int uid = reasons.first;
+ final int blockedReasons = reasons.second;
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ synchronized (mBlockedStatusTrackingUids) {
+ if (mBlockedStatusTrackingUids.get(uid) == 0) {
+ // This uid is not tracked anymore.
+ // This can happen if the network request is unregistered while
+ // EVENT_BLOCKED_REASONS_CHANGED is posted but not processed yet.
+ continue;
+ }
+ }
+ }
+ maybeNotifyNetworkBlockedForNewState(uid, blockedReasons);
+ setUidBlockedReasons(uid, blockedReasons);
+ }
}
static final class UidFrozenStateChangedArgs {
@@ -3347,44 +3433,92 @@
return !mNetworkActivityTracker.isDefaultNetworkActive();
}
- private void handleFrozenUids(int[] uids, int[] frozenStates) {
- final ArraySet<Integer> ownerUids = new ArraySet<>();
+ private boolean shouldTrackFirewallDestroySocketReasons() {
+ return mDeps.isAtLeastV();
+ }
- for (int i = 0; i < uids.length; i++) {
- if (frozenStates[i] == UID_FROZEN_STATE_FROZEN) {
- ownerUids.add(uids[i]);
- } else {
- mPendingFrozenUids.remove(uids[i]);
- }
- }
-
- if (ownerUids.isEmpty()) {
- return;
- }
-
- if (mDelayDestroyFrozenSockets && isCellNetworkIdle()) {
- // Delay closing sockets to avoid waking the cell modem up.
- // Wi-Fi network state is not considered since waking Wi-Fi modem up is much cheaper
- // than waking cell modem up.
- mPendingFrozenUids.addAll(ownerUids);
+ private void updateDestroySocketReasons(final int uid, final int reason,
+ final boolean addReason) {
+ final int destroyReasons = mDestroySocketPendingUids.get(uid, DESTROY_SOCKET_REASON_NONE);
+ if (addReason) {
+ mDestroySocketPendingUids.put(uid, destroyReasons | reason);
} else {
- try {
- mDeps.destroyLiveTcpSocketsByOwnerUids(ownerUids);
- } catch (SocketException | InterruptedIOException | ErrnoException e) {
- loge("Exception in socket destroy: " + e);
+ final int newDestroyReasons = destroyReasons & ~reason;
+ if (newDestroyReasons == DESTROY_SOCKET_REASON_NONE) {
+ mDestroySocketPendingUids.delete(uid);
+ } else {
+ mDestroySocketPendingUids.put(uid, newDestroyReasons);
}
}
}
- private void closePendingFrozenSockets() {
+ private void handleFrozenUids(int[] uids, int[] frozenStates) {
+ ensureRunningOnConnectivityServiceThread();
+ for (int i = 0; i < uids.length; i++) {
+ final int uid = uids[i];
+ final boolean addReason = frozenStates[i] == UID_FROZEN_STATE_FROZEN;
+ updateDestroySocketReasons(uid, DESTROY_SOCKET_REASON_FROZEN, addReason);
+ }
+
+ if (!mDelayDestroySockets || !isCellNetworkIdle()) {
+ destroyPendingSockets();
+ }
+ }
+
+ private void handleUpdateFirewallDestroySocketReasons(
+ List<Pair<Integer, Integer>> reasonsList) {
+ if (!shouldTrackFirewallDestroySocketReasons()) {
+ Log.wtf(TAG, "handleUpdateFirewallDestroySocketReasons is called unexpectedly");
+ return;
+ }
ensureRunningOnConnectivityServiceThread();
- try {
- mDeps.destroyLiveTcpSocketsByOwnerUids(mPendingFrozenUids);
- } catch (SocketException | InterruptedIOException | ErrnoException e) {
- loge("Failed to close pending frozen app sockets: " + e);
+ for (Pair<Integer, Integer> uidSocketDestroyReasons: reasonsList) {
+ final int uid = uidSocketDestroyReasons.first;
+ final int reasons = uidSocketDestroyReasons.second;
+ final boolean destroyByFirewallBackground =
+ (reasons & DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND)
+ != DESTROY_SOCKET_REASON_NONE;
+ updateDestroySocketReasons(uid, DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND,
+ destroyByFirewallBackground);
}
- mPendingFrozenUids.clear();
+
+ if (!mDelayDestroySockets || !isCellNetworkIdle()) {
+ destroyPendingSockets();
+ }
+ }
+
+ private void handleClearFirewallDestroySocketReasons(final int reason) {
+ if (!shouldTrackFirewallDestroySocketReasons()) {
+ Log.wtf(TAG, "handleClearFirewallDestroySocketReasons is called uexpectedly");
+ return;
+ }
+ ensureRunningOnConnectivityServiceThread();
+
+ // Unset reason from all pending uids
+ for (int i = mDestroySocketPendingUids.size() - 1; i >= 0; i--) {
+ final int uid = mDestroySocketPendingUids.keyAt(i);
+ updateDestroySocketReasons(uid, reason, false /* addReason */);
+ }
+ }
+
+ private void destroyPendingSockets() {
+ ensureRunningOnConnectivityServiceThread();
+ if (mDestroySocketPendingUids.size() == 0) {
+ return;
+ }
+
+ Set<Integer> uids = new ArraySet<>();
+ for (int i = 0; i < mDestroySocketPendingUids.size(); i++) {
+ uids.add(mDestroySocketPendingUids.keyAt(i));
+ }
+
+ try {
+ mDeps.destroyLiveTcpSocketsByOwnerUids(uids);
+ } catch (SocketException | InterruptedIOException | ErrnoException e) {
+ loge("Failed to destroy sockets: " + e);
+ }
+ mDestroySocketPendingUids.clear();
}
private void handleReportNetworkActivity(final NetworkActivityParams params) {
@@ -3401,43 +3535,46 @@
isCellNetworkActivity = params.label == TRANSPORT_CELLULAR;
}
- if (mDelayDestroyFrozenSockets
- && params.isActive
- && isCellNetworkActivity
- && !mPendingFrozenUids.isEmpty()) {
- closePendingFrozenSockets();
+ if (mDelayDestroySockets && params.isActive && isCellNetworkActivity) {
+ destroyPendingSockets();
}
}
/**
- * If the cellular network is no longer the default network, close pending frozen sockets.
+ * If the cellular network is no longer the default network, destroy pending sockets.
*
* @param newNetwork new default network
* @param oldNetwork old default network
*/
- private void maybeClosePendingFrozenSockets(NetworkAgentInfo newNetwork,
+ private void maybeDestroyPendingSockets(NetworkAgentInfo newNetwork,
NetworkAgentInfo oldNetwork) {
final boolean isOldNetworkCellular = oldNetwork != null
&& oldNetwork.networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
final boolean isNewNetworkCellular = newNetwork != null
&& newNetwork.networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
- if (isOldNetworkCellular
- && !isNewNetworkCellular
- && !mPendingFrozenUids.isEmpty()) {
- closePendingFrozenSockets();
+ if (isOldNetworkCellular && !isNewNetworkCellular) {
+ destroyPendingSockets();
}
}
- private void dumpCloseFrozenAppSockets(IndentingPrintWriter pw) {
- pw.println("CloseFrozenAppSockets:");
+ private void dumpDestroySockets(IndentingPrintWriter pw) {
+ pw.println("DestroySockets:");
pw.increaseIndent();
pw.print("mDestroyFrozenSockets="); pw.println(mDestroyFrozenSockets);
- pw.print("mDelayDestroyFrozenSockets="); pw.println(mDelayDestroyFrozenSockets);
- pw.print("mPendingFrozenUids="); pw.println(mPendingFrozenUids);
+ pw.print("mDelayDestroySockets="); pw.println(mDelayDestroySockets);
+ pw.print("mDestroySocketPendingUids:");
+ pw.increaseIndent();
+ for (int i = 0; i < mDestroySocketPendingUids.size(); i++) {
+ final int uid = mDestroySocketPendingUids.keyAt(i);
+ final int reasons = mDestroySocketPendingUids.valueAt(i);
+ pw.print(uid + ": reasons=" + reasons);
+ }
+ pw.decreaseIndent();
pw.decreaseIndent();
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private void dumpBpfProgramStatus(IndentingPrintWriter pw) {
pw.println("Bpf Program Status:");
pw.increaseIndent();
@@ -3446,12 +3583,37 @@
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_INGRESS));
pw.print("CGROUP_INET_EGRESS: ");
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_EGRESS));
+
pw.print("CGROUP_INET_SOCK_CREATE: ");
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_SOCK_CREATE));
+
pw.print("CGROUP_INET4_BIND: ");
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET4_BIND));
pw.print("CGROUP_INET6_BIND: ");
pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET6_BIND));
+
+ pw.print("CGROUP_INET4_CONNECT: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET4_CONNECT));
+ pw.print("CGROUP_INET6_CONNECT: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET6_CONNECT));
+
+ pw.print("CGROUP_UDP4_SENDMSG: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_UDP4_SENDMSG));
+ pw.print("CGROUP_UDP6_SENDMSG: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_UDP6_SENDMSG));
+
+ pw.print("CGROUP_UDP4_RECVMSG: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_UDP4_RECVMSG));
+ pw.print("CGROUP_UDP6_RECVMSG: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_UDP6_RECVMSG));
+
+ pw.print("CGROUP_GETSOCKOPT: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_GETSOCKOPT));
+ pw.print("CGROUP_SETSOCKOPT: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_SETSOCKOPT));
+
+ pw.print("CGROUP_INET_SOCK_RELEASE: ");
+ pw.println(mDeps.getBpfProgramId(BPF_CGROUP_INET_SOCK_RELEASE));
} catch (IOException e) {
pw.println(" IOException");
}
@@ -3460,9 +3622,6 @@
@VisibleForTesting
static final String KEY_DESTROY_FROZEN_SOCKETS_VERSION = "destroy_frozen_sockets_version";
- @VisibleForTesting
- static final String DELAY_DESTROY_FROZEN_SOCKETS_VERSION =
- "delay_destroy_frozen_sockets_version";
@VisibleForTesting
public static final String ALLOW_SYSUI_CONNECTIVITY_REPORTS =
@@ -4023,6 +4182,12 @@
boolean verbose = !CollectionUtils.contains(args, SHORT_ARG);
dumpTrafficController(pw, fd, verbose);
return;
+ } else if (CollectionUtils.contains(args, CLATEGRESS4RAWBPFMAP_ARG)) {
+ dumpClatBpfRawMap(pw, true /* isEgress4Map */);
+ return;
+ } else if (CollectionUtils.contains(args, CLATINGRESS6RAWBPFMAP_ARG)) {
+ dumpClatBpfRawMap(pw, false /* isEgress4Map */);
+ return;
}
pw.println("NetworkProviders for:");
@@ -4096,10 +4261,21 @@
dumpAvoidBadWifiSettings(pw);
pw.println();
- dumpCloseFrozenAppSockets(pw);
+ dumpDestroySockets(pw);
- pw.println();
- dumpBpfProgramStatus(pw);
+ if (mDeps.isAtLeastT()) {
+ // R: https://android.googlesource.com/platform/system/core/+/refs/heads/android11-release/rootdir/init.rc
+ // shows /dev/cg2_bpf
+ // S: https://android.googlesource.com/platform/system/core/+/refs/heads/android12-release/rootdir/init.rc
+ // does not
+ // Thus cgroups are mounted at /dev/cg2_bpf on R and not on /sys/fs/cgroup
+ // so the following won't work (on R) anyway.
+ // The /sys/fs/cgroup path is only actually enforced/required starting with U,
+ // but it is very likely to already be the case (though not guaranteed) on T.
+ // I'm not at all sure about S - let's just skip it to get rid of lint warnings.
+ pw.println();
+ dumpBpfProgramStatus(pw);
+ }
if (null != mCarrierPrivilegeAuthenticator) {
pw.println();
@@ -4282,6 +4458,15 @@
}
}
+ private void dumpClatBpfRawMap(IndentingPrintWriter pw, boolean isEgress4Map) {
+ for (NetworkAgentInfo nai : networksSortedById()) {
+ if (nai.clatd != null) {
+ nai.clatd.dumpRawBpfMap(pw, isEgress4Map);
+ break;
+ }
+ }
+ }
+
private void dumpAllRequestInfoLogsToLogcat() {
try (PrintWriter logPw = new PrintWriter(new Writer() {
@Override
@@ -4659,7 +4844,15 @@
final String logMsg = !TextUtils.isEmpty(redirectUrl)
? " with redirect to " + redirectUrl
: "";
- log(nai.toShortString() + " validation " + (valid ? "passed" : "failed") + logMsg);
+ final String statusMsg;
+ if (valid) {
+ statusMsg = "passed";
+ } else if (!TextUtils.isEmpty(redirectUrl)) {
+ statusMsg = "detected a portal";
+ } else {
+ statusMsg = "failed";
+ }
+ log(nai.toShortString() + " validation " + statusMsg + logMsg);
}
if (valid != wasValidated) {
final FullScore oldScore = nai.getScore();
@@ -5209,7 +5402,7 @@
if (mDefaultRequest == nri) {
mNetworkActivityTracker.updateDefaultNetwork(null /* newNetwork */, nai);
- maybeClosePendingFrozenSockets(null /* newNetwork */, nai);
+ maybeDestroyPendingSockets(null /* newNetwork */, nai);
ensureNetworkTransitionWakelock(nai.toShortString());
}
}
@@ -5486,6 +5679,12 @@
// If there is an active request, then for sure there is a satisfier.
nri.getSatisfier().addRequest(activeRequest);
}
+
+ if (shouldTrackUidsForBlockedStatusCallbacks()
+ && isAppRequest(nri)
+ && !nri.mUidTrackedForBlockedStatus) {
+ Log.wtf(TAG, "Registered nri is not tracked for sending blocked status: " + nri);
+ }
}
if (mFlags.noRematchAllRequestsOnRegister()) {
@@ -5685,6 +5884,11 @@
}
private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri) {
+ handleRemoveNetworkRequest(nri, true /* untrackUids */);
+ }
+
+ private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri,
+ final boolean untrackUids) {
ensureRunningOnConnectivityServiceThread();
for (final NetworkRequest req : nri.mRequests) {
if (null == mNetworkRequests.remove(req)) {
@@ -5719,7 +5923,9 @@
}
}
- nri.mPerUidCounter.decrementCount(nri.mUid);
+ if (untrackUids) {
+ maybeUntrackUidAndClearBlockedReasons(nri);
+ }
mNetworkRequestInfoLogs.log("RELEASE " + nri);
checkNrisConsistency(nri);
@@ -5741,12 +5947,17 @@
}
private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
+ handleRemoveNetworkRequests(nris, true /* untrackUids */);
+ }
+
+ private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris,
+ final boolean untrackUids) {
for (final NetworkRequestInfo nri : nris) {
if (mDefaultRequest == nri) {
// Make sure we never remove the default request.
continue;
}
- handleRemoveNetworkRequest(nri);
+ handleRemoveNetworkRequest(nri, untrackUids);
}
}
@@ -6486,8 +6697,8 @@
handlePrivateDnsValidationUpdate(
(PrivateDnsValidationUpdate) msg.obj);
break;
- case EVENT_UID_BLOCKED_REASON_CHANGED:
- handleUidBlockedReasonChanged(msg.arg1, msg.arg2);
+ case EVENT_BLOCKED_REASONS_CHANGED:
+ handleBlockedReasonsChanged((List) msg.obj);
break;
case EVENT_SET_REQUIRE_VPN_FOR_UIDS:
handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj);
@@ -6536,6 +6747,12 @@
UidFrozenStateChangedArgs args = (UidFrozenStateChangedArgs) msg.obj;
handleFrozenUids(args.mUids, args.mFrozenStates);
break;
+ case EVENT_UPDATE_FIREWALL_DESTROY_SOCKET_REASONS:
+ handleUpdateFirewallDestroySocketReasons((List) msg.obj);
+ break;
+ case EVENT_CLEAR_FIREWALL_DESTROY_SOCKET_REASONS:
+ handleClearFirewallDestroySocketReasons(msg.arg1);
+ break;
}
}
}
@@ -7352,9 +7569,16 @@
// maximum limit of registered callbacks per UID.
final int mAsUid;
+ // Flag to indicate that uid of this nri is tracked for sending blocked status callbacks.
+ // It is always true on V+ if mMessenger != null. As such, it's not strictly necessary.
+ // it's used only as a safeguard to avoid double counting or leaking.
+ boolean mUidTrackedForBlockedStatus;
+
// Preference order of this request.
final int mPreferenceOrder;
+ final int mDeclaredMethodsFlags;
+
// In order to preserve the mapping of NetworkRequest-to-callback when apps register
// callbacks using a returned NetworkRequest, the original NetworkRequest needs to be
// maintained for keying off of. This is only a concern when the original nri
@@ -7401,7 +7625,6 @@
mUid = mDeps.getCallingUid();
mAsUid = asUid;
mPerUidCounter = getRequestCounter(this);
- mPerUidCounter.incrementCountOrThrow(mUid);
/**
* Location sensitive data not included in pending intent. Only included in
* {@link NetworkCallback}.
@@ -7409,21 +7632,22 @@
mCallbackFlags = NetworkCallback.FLAG_NONE;
mCallingAttributionTag = callingAttributionTag;
mPreferenceOrder = preferenceOrder;
+ mDeclaredMethodsFlags = DECLARED_METHODS_NONE;
}
NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r, @Nullable final Messenger m,
@Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlags) {
this(asUid, Collections.singletonList(r), r, m, binder, callbackFlags,
- callingAttributionTag);
+ callingAttributionTag, declaredMethodsFlags);
}
NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r,
@NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m,
@Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlags) {
super();
ensureAllNetworkRequestsHaveType(r);
mRequests = initializeRequests(r);
@@ -7435,10 +7659,10 @@
mAsUid = asUid;
mPendingIntent = null;
mPerUidCounter = getRequestCounter(this);
- mPerUidCounter.incrementCountOrThrow(mUid);
mCallbackFlags = callbackFlags;
mCallingAttributionTag = callingAttributionTag;
mPreferenceOrder = PREFERENCE_ORDER_INVALID;
+ mDeclaredMethodsFlags = declaredMethodsFlags;
linkDeathRecipient();
}
@@ -7475,10 +7699,11 @@
mAsUid = nri.mAsUid;
mPendingIntent = nri.mPendingIntent;
mPerUidCounter = nri.mPerUidCounter;
- mPerUidCounter.incrementCountOrThrow(mUid);
mCallbackFlags = nri.mCallbackFlags;
mCallingAttributionTag = nri.mCallingAttributionTag;
+ mUidTrackedForBlockedStatus = nri.mUidTrackedForBlockedStatus;
mPreferenceOrder = PREFERENCE_ORDER_INVALID;
+ mDeclaredMethodsFlags = nri.mDeclaredMethodsFlags;
linkDeathRecipient();
}
@@ -7565,7 +7790,9 @@
+ " " + mRequests
+ (mPendingIntent == null ? "" : " to trigger " + mPendingIntent)
+ " callback flags: " + mCallbackFlags
- + " order: " + mPreferenceOrder;
+ + " order: " + mPreferenceOrder
+ + " isUidTracked: " + mUidTrackedForBlockedStatus
+ + " declaredMethods: 0x" + Integer.toHexString(mDeclaredMethodsFlags);
}
}
@@ -7703,7 +7930,21 @@
public NetworkRequest requestNetwork(int asUid, NetworkCapabilities networkCapabilities,
int reqTypeInt, Messenger messenger, int timeoutMs, final IBinder binder,
int legacyType, int callbackFlags, @NonNull String callingPackageName,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlag) {
+ if (declaredMethodsFlag == 0) {
+ // This could happen if raw binder calls are used to call the previous overload of
+ // requestNetwork, as missing int arguments in a binder call end up as 0
+ // (Parcel.readInt returns 0 at the end of a parcel). Such raw calls this would be
+ // really unexpected bad behavior from the caller though.
+ // TODO: remove after verifying this does not happen. This could allow enabling the
+ // optimization for callbacks that do not override any method (right now they use
+ // DECLARED_METHODS_ALL), if it is OK to break NetworkCallbacks created using
+ // dexmaker-mockito-inline and either spy() or MockSettings.useConstructor (see
+ // comment in ConnectivityManager which sets the flag to DECLARED_METHODS_ALL).
+ Log.wtf(TAG, "requestNetwork called without declaredMethodsFlag from "
+ + callingPackageName);
+ declaredMethodsFlag = DECLARED_METHODS_ALL;
+ }
if (legacyType != TYPE_NONE && !hasNetworkStackPermission()) {
if (isTargetSdkAtleast(Build.VERSION_CODES.M, mDeps.getCallingUid(),
callingPackageName)) {
@@ -7780,13 +8021,6 @@
throw new IllegalArgumentException("Bad timeout specified");
}
- final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
- nextNetworkRequestId(), reqType);
- final NetworkRequestInfo nri = getNriToRegister(
- asUid, networkRequest, messenger, binder, callbackFlags,
- callingAttributionTag);
- if (DBG) log("requestNetwork for " + nri);
-
// For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
// copied from the default request above. (This is necessary to ensure, for example, that
// the callback does not leak sensitive information to unprivileged apps.) Check that the
@@ -7798,7 +8032,13 @@
+ networkCapabilities + " vs. " + defaultNc);
}
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
+ final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
+ nextNetworkRequestId(), reqType);
+ final NetworkRequestInfo nri = getNriToRegister(
+ asUid, networkRequest, messenger, binder, callbackFlags,
+ callingAttributionTag, declaredMethodsFlag);
+ if (DBG) log("requestNetwork for " + nri);
+ trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_REQUEST, nri);
if (timeoutMs > 0) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST,
nri), timeoutMs);
@@ -7822,7 +8062,7 @@
private NetworkRequestInfo getNriToRegister(final int asUid, @NonNull final NetworkRequest nr,
@Nullable final Messenger msgr, @Nullable final IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @Nullable String callingAttributionTag) {
+ @Nullable String callingAttributionTag, int declaredMethodsFlags) {
final List<NetworkRequest> requests;
if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) {
requests = copyDefaultNetworkRequestsForUid(
@@ -7831,7 +8071,8 @@
requests = Collections.singletonList(nr);
}
return new NetworkRequestInfo(
- asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag);
+ asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag,
+ declaredMethodsFlags);
}
private boolean shouldCheckCapabilitiesDeclaration(
@@ -8007,8 +8248,7 @@
NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation,
callingAttributionTag);
if (DBG) log("pendingRequest for " + nri);
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
- nri));
+ trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT, nri);
return networkRequest;
}
@@ -8047,11 +8287,88 @@
return true;
}
+ private boolean isAppRequest(NetworkRequestInfo nri) {
+ return nri.mMessenger != null || nri.mPendingIntent != null;
+ }
+
+ private void trackUidAndMaybePostCurrentBlockedReason(final NetworkRequestInfo nri) {
+ if (!isAppRequest(nri)) {
+ Log.wtf(TAG, "trackUidAndMaybePostCurrentBlockedReason is called for non app"
+ + "request: " + nri);
+ return;
+ }
+ nri.mPerUidCounter.incrementCountOrThrow(nri.mUid);
+
+ // If nri.mMessenger is null, this nri does not have NetworkCallback so ConnectivityService
+ // does not need to send onBlockedStatusChanged callback for this uid and does not need to
+ // track the uid in mBlockedStatusTrackingUids
+ if (!shouldTrackUidsForBlockedStatusCallbacks() || nri.mMessenger == null) {
+ return;
+ }
+ if (nri.mUidTrackedForBlockedStatus) {
+ Log.wtf(TAG, "Nri is already tracked for sending blocked status: " + nri);
+ return;
+ }
+ nri.mUidTrackedForBlockedStatus = true;
+ synchronized (mBlockedStatusTrackingUids) {
+ final int uid = nri.mAsUid;
+ final int count = mBlockedStatusTrackingUids.get(uid, 0);
+ if (count == 0) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_BLOCKED_REASONS_CHANGED,
+ List.of(new Pair<>(uid, mBpfNetMaps.getUidNetworkingBlockedReasons(uid)))));
+ }
+ mBlockedStatusTrackingUids.put(uid, count + 1);
+ }
+ }
+
+ private void trackUidAndRegisterNetworkRequest(final int event, NetworkRequestInfo nri) {
+ // Post the update of the UID's blocked reasons before posting the message that registers
+ // the callback. This is necessary because if the callback immediately matches a request,
+ // the onBlockedStatusChanged must be called with the correct blocked reasons.
+ // Also, once trackUidAndMaybePostCurrentBlockedReason is called, the register network
+ // request event must be posted, because otherwise the counter for uid will never be
+ // decremented.
+ trackUidAndMaybePostCurrentBlockedReason(nri);
+ mHandler.sendMessage(mHandler.obtainMessage(event, nri));
+ }
+
+ private void maybeUntrackUidAndClearBlockedReasons(final NetworkRequestInfo nri) {
+ if (!isAppRequest(nri)) {
+ // Not an app request.
+ return;
+ }
+ nri.mPerUidCounter.decrementCount(nri.mUid);
+
+ if (!shouldTrackUidsForBlockedStatusCallbacks() || nri.mMessenger == null) {
+ return;
+ }
+ if (!nri.mUidTrackedForBlockedStatus) {
+ Log.wtf(TAG, "Nri is not tracked for sending blocked status: " + nri);
+ return;
+ }
+ nri.mUidTrackedForBlockedStatus = false;
+ synchronized (mBlockedStatusTrackingUids) {
+ final int count = mBlockedStatusTrackingUids.get(nri.mAsUid);
+ if (count > 1) {
+ mBlockedStatusTrackingUids.put(nri.mAsUid, count - 1);
+ } else {
+ mBlockedStatusTrackingUids.delete(nri.mAsUid);
+ mUidBlockedReasons.delete(nri.mAsUid);
+ }
+ }
+ }
+
@Override
public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, IBinder binder,
@NetworkCallback.Flag int callbackFlags,
- @NonNull String callingPackageName, @NonNull String callingAttributionTag) {
+ @NonNull String callingPackageName, @NonNull String callingAttributionTag,
+ int declaredMethodsFlag) {
+ if (declaredMethodsFlag == 0) {
+ Log.wtf(TAG, "listenForNetwork called without declaredMethodsFlag from "
+ + callingPackageName);
+ declaredMethodsFlag = DECLARED_METHODS_ALL;
+ }
final int callingUid = mDeps.getCallingUid();
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
@@ -8073,10 +8390,10 @@
NetworkRequest.Type.LISTEN);
NetworkRequestInfo nri =
new NetworkRequestInfo(callingUid, networkRequest, messenger, binder, callbackFlags,
- callingAttributionTag);
+ callingAttributionTag, declaredMethodsFlag);
if (VDBG) log("listenForNetwork for " + nri);
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
+ trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_LISTENER, nri);
return networkRequest;
}
@@ -8101,8 +8418,7 @@
callingAttributionTag);
if (VDBG) log("pendingListenForNetwork for " + nri);
- mHandler.sendMessage(mHandler.obtainMessage(
- EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT, nri));
+ trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT, nri);
}
/** Returns the next Network provider ID. */
@@ -9516,7 +9832,8 @@
configBuilder.setUpstreamSelector(nr);
final NetworkRequestInfo nri = new NetworkRequestInfo(
nai.creatorUid, nr, null /* messenger */, null /* binder */,
- 0 /* callbackFlags */, null /* attributionTag */);
+ 0 /* callbackFlags */, null /* attributionTag */,
+ DECLARED_METHODS_NONE);
if (null != oldSatisfier) {
// Set the old satisfier in the new NRI so that the rematch will see any changes
nri.setSatisfier(oldSatisfier, nr);
@@ -9887,6 +10204,11 @@
// are Type.LISTEN, but should not have NetworkCallbacks invoked.
return;
}
+ if (mUseDeclaredMethodsForCallbacksEnabled
+ && (nri.mDeclaredMethodsFlags & (1 << notificationType)) == 0) {
+ // No need to send the notification as the recipient method is not overridden
+ return;
+ }
final Bundle bundle = new Bundle();
// TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
// TODO: check if defensive copies of data is needed.
@@ -10085,7 +10407,7 @@
mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
}
mNetworkActivityTracker.updateDefaultNetwork(newDefaultNetwork, oldDefaultNetwork);
- maybeClosePendingFrozenSockets(newDefaultNetwork, oldDefaultNetwork);
+ maybeDestroyPendingSockets(newDefaultNetwork, oldDefaultNetwork);
mProxyTracker.setDefaultProxy(null != newDefaultNetwork
? newDefaultNetwork.linkProperties.getHttpProxy() : null);
resetHttpProxyForNonDefaultNetwork(oldDefaultNetwork);
@@ -11057,7 +11379,7 @@
final boolean metered = nai.networkCapabilities.isMetered();
final boolean vpnBlocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges);
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE,
- getBlockedState(blockedReasons, metered, vpnBlocked));
+ getBlockedState(nri.mAsUid, blockedReasons, metered, vpnBlocked));
}
// Notify the requests on this NAI that the network is now lingered.
@@ -11066,7 +11388,21 @@
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
}
- private static int getBlockedState(int reasons, boolean metered, boolean vpnBlocked) {
+ private int getPermissionBlockedState(final int uid, final int reasons) {
+ // Before V, the blocked reasons come from NPMS, and that code already behaves as if the
+ // change was disabled: apps without the internet permission will never be told they are
+ // blocked.
+ if (!mDeps.isAtLeastV()) return reasons;
+
+ if (hasInternetPermission(uid)) return reasons;
+
+ return mDeps.isChangeEnabled(NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION, uid)
+ ? reasons | BLOCKED_REASON_NETWORK_RESTRICTED
+ : BLOCKED_REASON_NONE;
+ }
+
+ private int getBlockedState(int uid, int reasons, boolean metered, boolean vpnBlocked) {
+ reasons = getPermissionBlockedState(uid, reasons);
if (!metered) reasons &= ~BLOCKED_METERED_REASON_MASK;
return vpnBlocked
? reasons | BLOCKED_REASON_LOCKDOWN_VPN
@@ -11107,8 +11443,10 @@
? isUidBlockedByVpn(nri.mAsUid, newBlockedUidRanges)
: oldVpnBlocked;
- final int oldBlockedState = getBlockedState(blockedReasons, oldMetered, oldVpnBlocked);
- final int newBlockedState = getBlockedState(blockedReasons, newMetered, newVpnBlocked);
+ final int oldBlockedState = getBlockedState(
+ nri.mAsUid, blockedReasons, oldMetered, oldVpnBlocked);
+ final int newBlockedState = getBlockedState(
+ nri.mAsUid, blockedReasons, newMetered, newVpnBlocked);
if (oldBlockedState != newBlockedState) {
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
newBlockedState);
@@ -11127,8 +11465,9 @@
final boolean vpnBlocked = isUidBlockedByVpn(uid, mVpnBlockedUidRanges);
final int oldBlockedState = getBlockedState(
- mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE), metered, vpnBlocked);
- final int newBlockedState = getBlockedState(blockedReasons, metered, vpnBlocked);
+ uid, mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE), metered, vpnBlocked);
+ final int newBlockedState =
+ getBlockedState(uid, blockedReasons, metered, vpnBlocked);
if (oldBlockedState == newBlockedState) {
continue;
}
@@ -11494,6 +11833,10 @@
return 0;
}
case "get-package-networking-enabled": {
+ if (!mDeps.isAtLeastT()) {
+ throw new UnsupportedOperationException(
+ "This command is not supported on T-");
+ }
final String packageName = getNextArg();
final int rule = getPackageFirewallRule(
ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3, packageName);
@@ -11523,6 +11866,10 @@
return 0;
}
case "get-background-networking-enabled-for-uid": {
+ if (!mDeps.isAtLeastT()) {
+ throw new UnsupportedOperationException(
+ "This command is not supported on T-");
+ }
final Integer uid = parseIntegerArgument(getNextArg());
if (null == uid) {
onHelp();
@@ -12164,6 +12511,7 @@
// handleRegisterConnectivityDiagnosticsCallback(). nri will be cleaned up as part of the
// callback's binder death.
final NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, requestWithId);
+ nri.mPerUidCounter.incrementCountOrThrow(nri.mUid);
final ConnectivityDiagnosticsCallbackInfo cbInfo =
new ConnectivityDiagnosticsCallbackInfo(callback, nri, callingPackageName);
@@ -13025,11 +13373,12 @@
requests.add(createDefaultInternetRequestForTransport(
TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
- // request: restricted Satellite internet
+ // request: Satellite internet, satellite network could be restricted or constrained
final NetworkCapabilities cap = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .removeCapability(NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE)
.build();
requests.add(createNetworkRequest(NetworkRequest.Type.REQUEST, cap));
@@ -13258,7 +13607,20 @@
final ArraySet<NetworkRequestInfo> perAppCallbackRequestsToUpdate =
getPerAppCallbackRequestsToUpdate();
final ArraySet<NetworkRequestInfo> nrisToRegister = new ArraySet<>(nris);
- handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate);
+ // This method does not need to modify perUidCounter and mBlockedStatusTrackingUids because:
+ // - |nris| only contains per-app network requests created by ConnectivityService which
+ // are internal requests and have no messenger and are not associated with any callbacks,
+ // and so do not need to be tracked in perUidCounter and mBlockedStatusTrackingUids.
+ // - The requests in perAppCallbackRequestsToUpdate are removed, modified, and re-added,
+ // but the same number of requests is removed and re-added, and none of the requests
+ // changes mUid and mAsUid, so the perUidCounter and mBlockedStatusTrackingUids before
+ // and after this method remains the same. Re-adding the requests does not modify
+ // perUidCounter and mBlockedStatusTrackingUids (that is done when the app registers the
+ // request), so removing them must not modify perUidCounter and mBlockedStatusTrackingUids
+ // either.
+ // TODO(b/341228979): Modify nris in place instead of removing them and re-adding them
+ handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate,
+ false /* untrackUids */);
nrisToRegister.addAll(
createPerAppCallbackRequestsToRegister(perAppCallbackRequestsToUpdate));
handleRegisterNetworkRequests(nrisToRegister);
@@ -13493,10 +13855,17 @@
throw new IllegalStateException(e);
}
- try {
- mBpfNetMaps.setDataSaverEnabled(enable);
- } catch (ServiceSpecificException | UnsupportedOperationException e) {
- Log.e(TAG, "Failed to set data saver " + enable + " : " + e);
+ synchronized (mBlockedStatusTrackingUids) {
+ try {
+ mBpfNetMaps.setDataSaverEnabled(enable);
+ } catch (ServiceSpecificException | UnsupportedOperationException e) {
+ Log.e(TAG, "Failed to set data saver " + enable + " : " + e);
+ return;
+ }
+
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ updateTrackingUidsBlockedReasons();
+ }
}
}
@@ -13532,13 +13901,24 @@
throw new IllegalArgumentException("setUidFirewallRule with invalid rule: " + rule);
}
- try {
- mBpfNetMaps.setUidRule(chain, uid, firewallRule);
- } catch (ServiceSpecificException e) {
- throw new IllegalStateException(e);
+ synchronized (mBlockedStatusTrackingUids) {
+ try {
+ mBpfNetMaps.setUidRule(chain, uid, firewallRule);
+ } catch (ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ if (shouldTrackUidsForBlockedStatusCallbacks()
+ && mBlockedStatusTrackingUids.get(uid, 0) != 0) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_BLOCKED_REASONS_CHANGED,
+ List.of(new Pair<>(uid, mBpfNetMaps.getUidNetworkingBlockedReasons(uid)))));
+ }
+ if (shouldTrackFirewallDestroySocketReasons()) {
+ maybePostFirewallDestroySocketReasons(chain, Set.of(uid));
+ }
}
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private int getPackageFirewallRule(final int chain, final String packageName)
throws PackageManager.NameNotFoundException {
final PackageManager pm = mContext.getPackageManager();
@@ -13546,6 +13926,7 @@
return getUidFirewallRule(chain, appId);
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Override
public int getUidFirewallRule(final int chain, final int uid) {
enforceNetworkStackOrSettingsPermission();
@@ -13580,23 +13961,40 @@
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ private Set<Integer> getUidsOnFirewallChain(final int chain) throws ErrnoException {
+ if (BpfNetMapsUtils.isFirewallAllowList(chain)) {
+ return mBpfNetMaps.getUidsWithAllowRuleOnAllowListChain(chain);
+ } else {
+ return mBpfNetMaps.getUidsWithDenyRuleOnDenyListChain(chain);
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private void closeSocketsForFirewallChainLocked(final int chain)
throws ErrnoException, SocketException, InterruptedIOException {
+ final Set<Integer> uidsOnChain = getUidsOnFirewallChain(chain);
if (BpfNetMapsUtils.isFirewallAllowList(chain)) {
// Allowlist means the firewall denies all by default, uids must be explicitly allowed
// So, close all non-system socket owned by uids that are not explicitly allowed
Set<Range<Integer>> ranges = new ArraySet<>();
ranges.add(new Range<>(Process.FIRST_APPLICATION_UID, Integer.MAX_VALUE));
- final Set<Integer> exemptUids = mBpfNetMaps.getUidsWithAllowRuleOnAllowListChain(chain);
- mDeps.destroyLiveTcpSockets(ranges, exemptUids);
+ mDeps.destroyLiveTcpSockets(ranges, uidsOnChain /* exemptUids */);
} else {
// Denylist means the firewall allows all by default, uids must be explicitly denied
// So, close socket owned by uids that are explicitly denied
- final Set<Integer> ownerUids = mBpfNetMaps.getUidsWithDenyRuleOnDenyListChain(chain);
- mDeps.destroyLiveTcpSocketsByOwnerUids(ownerUids);
+ mDeps.destroyLiveTcpSocketsByOwnerUids(uidsOnChain /* ownerUids */);
}
}
+ private void maybePostClearFirewallDestroySocketReasons(int chain) {
+ if (chain != FIREWALL_CHAIN_BACKGROUND) {
+ // TODO (b/300681644): Support other firewall chains
+ return;
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_CLEAR_FIREWALL_DESTROY_SOCKET_REASONS,
+ DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND, 0 /* arg2 */));
+ }
+
@Override
public void setFirewallChainEnabled(final int chain, final boolean enable) {
enforceNetworkStackOrSettingsPermission();
@@ -13613,10 +14011,20 @@
"Chain (" + chain + ") can not be controlled by setFirewallChainEnabled");
}
- try {
- mBpfNetMaps.setChildChain(chain, enable);
- } catch (ServiceSpecificException e) {
- throw new IllegalStateException(e);
+ synchronized (mBlockedStatusTrackingUids) {
+ try {
+ mBpfNetMaps.setChildChain(chain, enable);
+ } catch (ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ updateTrackingUidsBlockedReasons();
+ }
+ if (shouldTrackFirewallDestroySocketReasons() && !enable) {
+ // Clear destroy socket reasons so that CS does not destroy sockets of apps that
+ // have network access.
+ maybePostClearFirewallDestroySocketReasons(chain);
+ }
}
if (mDeps.isAtLeastU() && enable) {
@@ -13628,6 +14036,47 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @GuardedBy("mBlockedStatusTrackingUids")
+ private void updateTrackingUidsBlockedReasons() {
+ if (mBlockedStatusTrackingUids.size() == 0) {
+ return;
+ }
+ final ArrayList<Pair<Integer, Integer>> uidBlockedReasonsList = new ArrayList<>();
+ for (int i = 0; i < mBlockedStatusTrackingUids.size(); i++) {
+ final int uid = mBlockedStatusTrackingUids.keyAt(i);
+ uidBlockedReasonsList.add(
+ new Pair<>(uid, mBpfNetMaps.getUidNetworkingBlockedReasons(uid)));
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_BLOCKED_REASONS_CHANGED,
+ uidBlockedReasonsList));
+ }
+
+ private int getFirewallDestroySocketReasons(final int blockedReasons) {
+ int destroySocketReasons = DESTROY_SOCKET_REASON_NONE;
+ if ((blockedReasons & BLOCKED_REASON_APP_BACKGROUND) != BLOCKED_REASON_NONE) {
+ destroySocketReasons |= DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND;
+ }
+ return destroySocketReasons;
+ }
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @GuardedBy("mBlockedStatusTrackingUids")
+ private void maybePostFirewallDestroySocketReasons(int chain, Set<Integer> uids) {
+ if (chain != FIREWALL_CHAIN_BACKGROUND) {
+ // TODO (b/300681644): Support other firewall chains
+ return;
+ }
+ final ArrayList<Pair<Integer, Integer>> reasonsList = new ArrayList<>();
+ for (int uid: uids) {
+ final int blockedReasons = mBpfNetMaps.getUidNetworkingBlockedReasons(uid);
+ final int destroySocketReaons = getFirewallDestroySocketReasons(blockedReasons);
+ reasonsList.add(new Pair<>(uid, destroySocketReaons));
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_UPDATE_FIREWALL_DESTROY_SOCKET_REASONS,
+ reasonsList));
+ }
+
@Override
public boolean getFirewallChainEnabled(final int chain) {
enforceNetworkStackOrSettingsPermission();
@@ -13652,7 +14101,31 @@
return;
}
- mBpfNetMaps.replaceUidChain(chain, uids);
+ synchronized (mBlockedStatusTrackingUids) {
+ // replaceFirewallChain removes uids that are currently on the chain and put |uids| on
+ // the chain.
+ // So this method could change blocked reasons of uids that are currently on chain +
+ // |uids|.
+ final Set<Integer> affectedUids = new ArraySet<>();
+ if (shouldTrackFirewallDestroySocketReasons()) {
+ try {
+ affectedUids.addAll(getUidsOnFirewallChain(chain));
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to get uids on chain(" + chain + "): " + e);
+ }
+ for (final int uid: uids) {
+ affectedUids.add(uid);
+ }
+ }
+
+ mBpfNetMaps.replaceUidChain(chain, uids);
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ updateTrackingUidsBlockedReasons();
+ }
+ if (shouldTrackFirewallDestroySocketReasons()) {
+ maybePostFirewallDestroySocketReasons(chain, affectedUids);
+ }
+ }
}
@Override
@@ -13666,4 +14139,16 @@
enforceNetworkStackPermission(mContext);
return mRoutingCoordinatorService;
}
+
+ @Override
+ public long getEnabledConnectivityManagerFeatures() {
+ long features = 0;
+ // The bitmask must be built based on final properties initialized in the constructor, to
+ // ensure that it does not change over time and is always consistent between
+ // ConnectivityManager and ConnectivityService.
+ if (mUseDeclaredMethodsForCallbacksEnabled) {
+ features |= ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS;
+ }
+ return features;
+ }
}
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index 843b7b3..4d39d7d 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -267,6 +267,7 @@
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
nc.setNetworkSpecifier(new TestNetworkSpecifier(iface));
nc.setAdministratorUids(administratorUids);
if (!isMetered) {
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 634a8fa..b1c770b 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -41,9 +41,11 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.BpfDump;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.SingleWriterBpfMap;
import com.android.net.module.util.TcUtils;
import com.android.net.module.util.bpf.ClatEgress4Key;
import com.android.net.module.util.bpf.ClatEgress4Value;
@@ -255,7 +257,7 @@
@Nullable
public IBpfMap<ClatIngress6Key, ClatIngress6Value> getBpfIngress6Map() {
try {
- return new BpfMap<>(CLAT_INGRESS6_MAP_PATH,
+ return SingleWriterBpfMap.getSingleton(CLAT_INGRESS6_MAP_PATH,
ClatIngress6Key.class, ClatIngress6Value.class);
} catch (ErrnoException e) {
Log.e(TAG, "Cannot create ingress6 map: " + e);
@@ -267,7 +269,7 @@
@Nullable
public IBpfMap<ClatEgress4Key, ClatEgress4Value> getBpfEgress4Map() {
try {
- return new BpfMap<>(CLAT_EGRESS4_MAP_PATH,
+ return SingleWriterBpfMap.getSingleton(CLAT_EGRESS4_MAP_PATH,
ClatEgress4Key.class, ClatEgress4Value.class);
} catch (ErrnoException e) {
Log.e(TAG, "Cannot create egress4 map: " + e);
@@ -279,6 +281,7 @@
@Nullable
public IBpfMap<CookieTagMapKey, CookieTagMapValue> getBpfCookieTagMap() {
try {
+ // also read and written from other locations
return new BpfMap<>(COOKIE_TAG_MAP_PATH,
CookieTagMapKey.class, CookieTagMapValue.class);
} catch (ErrnoException e) {
@@ -659,6 +662,8 @@
final int pid = mDeps.startClatd(tunFd.getFileDescriptor(),
readSock6.getFileDescriptor(), writeSock6.getFileDescriptor(), iface, pfx96Str,
v4Str, v6Str);
+ // The file descriptors have been duplicated (dup2) to clatd in native_startClatd().
+ // Close these file descriptor stubs in finally block.
// [6] Initialize and store clatd tracker object.
mClatdTracker = new ClatdTracker(iface, ifIndex, tunIface, tunIfIndex, v4, v6, pfx96,
@@ -677,20 +682,29 @@
Log.e(TAG, "untagSocket cookie " + cookie + " failed: " + e2);
}
}
- try {
- if (tunFd != null) {
- tunFd.close();
- }
- if (readSock6 != null) {
- readSock6.close();
- }
- if (writeSock6 != null) {
- writeSock6.close();
- }
- } catch (IOException e2) {
- Log.e(TAG, "Fail to cleanup fd ", e);
- }
throw new IOException("Failed to start clat ", e);
+ } finally {
+ if (tunFd != null) {
+ try {
+ tunFd.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close tun file descriptor " + e);
+ }
+ }
+ if (readSock6 != null) {
+ try {
+ readSock6.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close read socket " + e);
+ }
+ }
+ if (writeSock6 != null) {
+ try {
+ writeSock6.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close write socket " + e);
+ }
+ }
}
}
@@ -795,6 +809,29 @@
}
/**
+ * Dump raw BPF map into base64 encoded strings {@literal "<base64 key>,<base64 value>"}.
+ * Allow to dump only one map in each call. For test only.
+ *
+ * @param pw print writer.
+ * @param isEgress4Map whether to dump the egress4 map (true) or the ingress6 map (false).
+ *
+ * Usage:
+ * $ dumpsys connectivity {clatEgress4RawBpfMap|clatIngress6RawBpfMap}
+ *
+ * Output:
+ * {@literal <base64 encoded key #1>,<base64 encoded value #1>}
+ * {@literal <base64 encoded key #2>,<base64 encoded value #2>}
+ * ..
+ */
+ public void dumpRawMap(@NonNull IndentingPrintWriter pw, boolean isEgress4Map) {
+ if (isEgress4Map) {
+ BpfDump.dumpRawMap(mEgressMap, pw);
+ } else {
+ BpfDump.dumpRawMap(mIngressMap, pw);
+ }
+ }
+
+ /**
* Dump the coordinator information.
*
* @param pw print writer.
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index 176307d..1ee1ed7 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -44,6 +44,11 @@
public static final String BACKGROUND_FIREWALL_CHAIN = "background_firewall_chain";
+ public static final String DELAY_DESTROY_SOCKETS = "delay_destroy_sockets";
+
+ public static final String USE_DECLARED_METHODS_FOR_CALLBACKS =
+ "use_declared_methods_for_callbacks";
+
private boolean mNoRematchAllRequestsOnRegister;
/**
diff --git a/service/src/com/android/server/connectivity/DnsManager.java b/service/src/com/android/server/connectivity/DnsManager.java
index 8e6854a..ac02229 100644
--- a/service/src/com/android/server/connectivity/DnsManager.java
+++ b/service/src/com/android/server/connectivity/DnsManager.java
@@ -390,6 +390,7 @@
: new String[0]; // Off
paramsParcel.transportTypes = nc.getTransportTypes();
paramsParcel.meteredNetwork = nc.isMetered();
+ paramsParcel.interfaceNames = lp.getAllInterfaceNames().toArray(new String[0]);
// Prepare to track the validation status of the DNS servers in the
// resolver config when private DNS is in opportunistic or strict mode.
if (useTls) {
@@ -403,13 +404,14 @@
}
Log.d(TAG, String.format("sendDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, "
- + "%d, %d, %s, %s, %s, %b)", paramsParcel.netId,
+ + "%d, %d, %s, %s, %s, %b, %s)", paramsParcel.netId,
Arrays.toString(paramsParcel.servers), Arrays.toString(paramsParcel.domains),
paramsParcel.sampleValiditySeconds, paramsParcel.successThreshold,
paramsParcel.minSamples, paramsParcel.maxSamples, paramsParcel.baseTimeoutMsec,
paramsParcel.retryCount, paramsParcel.tlsName,
Arrays.toString(paramsParcel.tlsServers),
- Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork));
+ Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork,
+ Arrays.toString(paramsParcel.interfaceNames)));
try {
mDnsResolver.setResolverConfiguration(paramsParcel);
diff --git a/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java b/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
index ac479b8..af4aee5 100644
--- a/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
+++ b/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
@@ -168,14 +168,18 @@
public void applyMulticastRoutingConfig(
final String iifName, final String oifName, final MulticastRoutingConfig newConfig) {
checkOnHandlerThread();
+ Objects.requireNonNull(iifName, "IifName can't be null");
+ Objects.requireNonNull(oifName, "OifName can't be null");
if (newConfig.getForwardingMode() != FORWARD_NONE) {
// Make sure iif and oif are added as multicast forwarding interfaces
- try {
- maybeAddAndTrackInterface(iifName);
- maybeAddAndTrackInterface(oifName);
- } catch (IllegalStateException e) {
- Log.e(TAG, "Failed to apply multicast routing config, ", e);
+ if (!maybeAddAndTrackInterface(iifName) || !maybeAddAndTrackInterface(oifName)) {
+ Log.e(
+ TAG,
+ "Failed to apply multicast routing config from "
+ + iifName
+ + " to "
+ + oifName);
return;
}
}
@@ -258,9 +262,14 @@
}
}
+ /**
+ * Returns the next available virtual index for multicast routing, or -1 if the number of
+ * virtual interfaces has reached max value.
+ */
private int getNextAvailableVirtualIndex() {
if (mVirtualInterfaces.size() >= MAX_NUM_OF_MULTICAST_VIRTUAL_INTERFACES) {
- throw new IllegalStateException("Can't allocate new multicast virtual interface");
+ Log.e(TAG, "Can't allocate new multicast virtual interface");
+ return -1;
}
for (int i = 0; i < mVirtualInterfaces.size(); i++) {
if (!mVirtualInterfaces.contains(i)) {
@@ -291,12 +300,23 @@
return mVirtualInterfaces.get(virtualIndex);
}
- private void maybeAddAndTrackInterface(String ifName) {
+ /**
+ * Returns {@code true} if the interfaces is added and tracked, or {@code false} when failed
+ * to add the interface.
+ */
+ private boolean maybeAddAndTrackInterface(String ifName) {
checkOnHandlerThread();
- if (getIndexForValue(mVirtualInterfaces, ifName) >= 0) return;
+ if (getIndexForValue(mVirtualInterfaces, ifName) >= 0) return true;
int nextVirtualIndex = getNextAvailableVirtualIndex();
+ if (nextVirtualIndex < 0) {
+ return false;
+ }
int ifIndex = mDependencies.getInterfaceIndex(ifName);
+ if (ifIndex == 0) {
+ Log.e(TAG, "Can't get interface index for " + ifName);
+ return false;
+ }
final StructMif6ctl mif6ctl =
new StructMif6ctl(
nextVirtualIndex,
@@ -309,10 +329,11 @@
Log.d(TAG, "Added mifi " + nextVirtualIndex + " to MIF");
} catch (ErrnoException e) {
Log.e(TAG, "failed to add multicast virtual interface", e);
- return;
+ return false;
}
mVirtualInterfaces.put(nextVirtualIndex, ifName);
mInterfaces.put(ifIndex, ifName);
+ return true;
}
@VisibleForTesting
@@ -798,13 +819,12 @@
NetworkUtils.setsockoptBytes(fd, IPPROTO_IPV6, MRT6_DEL_MFC, bytes);
}
- public Integer getInterfaceIndex(String ifName) {
- try {
- NetworkInterface ni = NetworkInterface.getByName(ifName);
- return ni.getIndex();
- } catch (NullPointerException | SocketException e) {
- return null;
- }
+ /**
+ * Returns the interface index for an interface name, or 0 if the interface index could
+ * not be found.
+ */
+ public int getInterfaceIndex(String ifName) {
+ return Os.if_nametoindex(ifName);
}
public NetworkInterface getNetworkInterface(int physicalIndex) {
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index 489dab5..a979681 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -622,6 +622,18 @@
}
}
+ /**
+ * Dump the raw BPF maps in 464XLAT
+ *
+ * @param pw print writer.
+ * @param isEgress4Map whether to dump the egress4 map (true) or the ingress6 map (false).
+ */
+ public void dumpRawBpfMap(IndentingPrintWriter pw, boolean isEgress4Map) {
+ if (SdkLevel.isAtLeastT()) {
+ mClatCoordinator.dumpRawMap(pw, isEgress4Map);
+ }
+ }
+
@Override
public String toString() {
return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState;
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index 7707122..fd41ee6 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -170,9 +170,11 @@
&& !TextUtils.isEmpty(nai.linkProperties.getCaptivePortalData()
.getVenueFriendlyName())) {
name = nai.linkProperties.getCaptivePortalData().getVenueFriendlyName();
+ } else if (!TextUtils.isEmpty(extraInfo)) {
+ name = extraInfo;
} else {
- name = TextUtils.isEmpty(extraInfo)
- ? WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()) : extraInfo;
+ final String ssid = WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid());
+ name = ssid == null ? "" : ssid;
}
// Only notify for Internet-capable networks.
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index ede6d3f..34ea9ab 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -39,12 +39,13 @@
"device/com/android/net/module/util/DeviceConfigUtils.java",
"device/com/android/net/module/util/DomainUtils.java",
"device/com/android/net/module/util/FdEventsReader.java",
+ "device/com/android/net/module/util/FeatureVersions.java",
+ "device/com/android/net/module/util/HandlerUtils.java",
"device/com/android/net/module/util/NetworkMonitorUtils.java",
"device/com/android/net/module/util/PacketReader.java",
"device/com/android/net/module/util/SharedLog.java",
"device/com/android/net/module/util/SocketUtils.java",
- "device/com/android/net/module/util/FeatureVersions.java",
- "device/com/android/net/module/util/HandlerUtils.java",
+ "device/com/android/net/module/util/SyncStateMachine.java",
// This library is used by system modules, for which the system health impact of Kotlin
// has not yet been evaluated. Annotations may need jarjar'ing.
// "src_devicecommon/**/*.kt",
@@ -68,6 +69,7 @@
"//packages/modules/CaptivePortalLogin",
],
static_libs: [
+ "modules-utils-statemachine",
"net-utils-framework-common",
],
libs: [
@@ -135,6 +137,7 @@
"device/com/android/net/module/util/BpfUtils.java",
"device/com/android/net/module/util/IBpfMap.java",
"device/com/android/net/module/util/JniUtil.java",
+ "device/com/android/net/module/util/SingleWriterBpfMap.java",
"device/com/android/net/module/util/TcUtils.java",
],
sdk_version: "module_current",
@@ -279,7 +282,7 @@
"//apex_available:platform",
],
lint: {
- baseline_filename: "lint-baseline.xml",
+ strict_updatability_linting: true,
error_checks: ["NewApi"],
},
}
diff --git a/staticlibs/device/com/android/net/module/util/BpfDump.java b/staticlibs/device/com/android/net/module/util/BpfDump.java
index 7549e71..4227194 100644
--- a/staticlibs/device/com/android/net/module/util/BpfDump.java
+++ b/staticlibs/device/com/android/net/module/util/BpfDump.java
@@ -81,6 +81,26 @@
}
/**
+ * Dump the BpfMap entries with base64 format encoding.
+ */
+ public static <K extends Struct, V extends Struct> void dumpRawMap(IBpfMap<K, V> map,
+ PrintWriter pw) {
+ try {
+ if (map == null) {
+ pw.println("BPF map is null");
+ return;
+ }
+ if (map.isEmpty()) {
+ pw.println("No entries");
+ return;
+ }
+ map.forEach((k, v) -> pw.println(toBase64EncodedString(k, v)));
+ } catch (ErrnoException e) {
+ pw.println("Map dump end with error: " + Os.strerror(e.errno));
+ }
+ }
+
+ /**
* Dump the BpfMap name and entries
*/
public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map,
diff --git a/staticlibs/device/com/android/net/module/util/BpfMap.java b/staticlibs/device/com/android/net/module/util/BpfMap.java
index da77ae8..0fbc25d 100644
--- a/staticlibs/device/com/android/net/module/util/BpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/BpfMap.java
@@ -15,6 +15,7 @@
*/
package com.android.net.module.util;
+import static android.system.OsConstants.EBUSY;
import static android.system.OsConstants.EEXIST;
import static android.system.OsConstants.ENOENT;
@@ -52,6 +53,9 @@
public static final int BPF_F_RDONLY = 1 << 3;
public static final int BPF_F_WRONLY = 1 << 4;
+ // magic value for jni consumption, invalid from kernel point of view
+ public static final int BPF_F_RDWR_EXCLUSIVE = BPF_F_RDONLY | BPF_F_WRONLY;
+
public static final int BPF_MAP_TYPE_HASH = 1;
private static final int BPF_F_NO_PREALLOC = 1;
@@ -69,6 +73,12 @@
private static ConcurrentHashMap<Pair<String, Integer>, ParcelFileDescriptor> sFdCache =
new ConcurrentHashMap<>();
+ private static ParcelFileDescriptor checkModeExclusivity(ParcelFileDescriptor fd, int mode)
+ throws ErrnoException {
+ if (mode == BPF_F_RDWR_EXCLUSIVE) throw new ErrnoException("cachedBpfFdGet", EBUSY);
+ return fd;
+ }
+
private static ParcelFileDescriptor cachedBpfFdGet(String path, int mode,
int keySize, int valueSize)
throws ErrnoException, NullPointerException {
@@ -79,12 +89,12 @@
var key = Pair.create(path, (mode << 26) ^ (keySize << 16) ^ valueSize);
// unlocked fetch is safe: map is concurrent read capable, and only inserted into
ParcelFileDescriptor fd = sFdCache.get(key);
- if (fd != null) return fd;
+ if (fd != null) return checkModeExclusivity(fd, mode);
// ok, no cached fd present, need to grab a lock
synchronized (BpfMap.class) {
// need to redo the check
fd = sFdCache.get(key);
- if (fd != null) return fd;
+ if (fd != null) return checkModeExclusivity(fd, mode);
// okay, we really haven't opened this before...
fd = ParcelFileDescriptor.adoptFd(nativeBpfFdGet(path, mode, keySize, valueSize));
sFdCache.put(key, fd);
diff --git a/staticlibs/device/com/android/net/module/util/BpfUtils.java b/staticlibs/device/com/android/net/module/util/BpfUtils.java
index cdd6fd7..a41eeba 100644
--- a/staticlibs/device/com/android/net/module/util/BpfUtils.java
+++ b/staticlibs/device/com/android/net/module/util/BpfUtils.java
@@ -39,6 +39,15 @@
public static final int BPF_CGROUP_INET_SOCK_CREATE = 2;
public static final int BPF_CGROUP_INET4_BIND = 8;
public static final int BPF_CGROUP_INET6_BIND = 9;
+ public static final int BPF_CGROUP_INET4_CONNECT = 10;
+ public static final int BPF_CGROUP_INET6_CONNECT = 11;
+ public static final int BPF_CGROUP_UDP4_SENDMSG = 14;
+ public static final int BPF_CGROUP_UDP6_SENDMSG = 15;
+ public static final int BPF_CGROUP_UDP4_RECVMSG = 19;
+ public static final int BPF_CGROUP_UDP6_RECVMSG = 20;
+ public static final int BPF_CGROUP_GETSOCKOPT = 21;
+ public static final int BPF_CGROUP_SETSOCKOPT = 22;
+ public static final int BPF_CGROUP_INET_SOCK_RELEASE = 34;
// Note: This is only guaranteed to be accurate on U+ devices. It is likely to be accurate
// on T+ devices as well, but this is not guaranteed.
diff --git a/staticlibs/device/com/android/net/module/util/PacketBuilder.java b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
index 33e5bfa..a2dbd81 100644
--- a/staticlibs/device/com/android/net/module/util/PacketBuilder.java
+++ b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
@@ -24,10 +24,13 @@
import static com.android.net.module.util.IpUtils.ipChecksum;
import static com.android.net.module.util.IpUtils.tcpChecksum;
import static com.android.net.module.util.IpUtils.udpChecksum;
+import static com.android.net.module.util.NetworkStackConstants.IPPROTO_FRAGMENT;
import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_FRAGMENT_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV6_LEN_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.TCP_CHECKSUM_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.UDP_CHECKSUM_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.UDP_LENGTH_OFFSET;
@@ -37,6 +40,7 @@
import androidx.annotation.NonNull;
import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.FragmentHeader;
import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.TcpHeader;
@@ -47,6 +51,10 @@
import java.net.Inet6Address;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
/**
* The class is used to build a packet.
@@ -210,6 +218,20 @@
}
}
+ private void writeFragmentHeader(ByteBuffer buffer, short nextHeader, int offset,
+ boolean mFlag, int id) throws IOException {
+ if ((offset & 7) != 0) {
+ throw new IOException("Invalid offset value, must be multiple of 8");
+ }
+ final FragmentHeader fragmentHeader = new FragmentHeader(nextHeader,
+ offset | (mFlag ? 1 : 0), id);
+ try {
+ fragmentHeader.writeToByteBuffer(buffer);
+ } catch (IllegalArgumentException | BufferOverflowException e) {
+ throw new IOException("Error writing to buffer: ", e);
+ }
+ }
+
/**
* Finalize the packet.
*
@@ -219,9 +241,31 @@
*/
@NonNull
public ByteBuffer finalizePacket() throws IOException {
+ // If the packet is finalized with L2 mtu greater than or equal to its current size, it will
+ // either return a List of size 1 or throw an IOException if something goes wrong.
+ return finalizePacket(mBuffer.position()).get(0);
+ }
+
+ /**
+ * Finalizes the packet with specified link MTU.
+ *
+ * Call after writing L4 header (no payload) or L4 payload to the buffer used by the builder.
+ * L3 header length, L3 header checksum and L4 header checksum are calculated and written back
+ * after finalization.
+ *
+ * @param l2mtu the maximum size, in bytes, of each individual packet. If the packet size
+ * exceeds the l2mtu, it will be fragmented into smaller packets.
+ * @return a list of packet(s), each containing a portion of the original L3 payload.
+ */
+ @NonNull
+ public List<ByteBuffer> finalizePacket(int l2mtu) throws IOException {
// [1] Finalize IPv4 or IPv6 header.
int ipHeaderOffset = INVALID_OFFSET;
if (mIpv4HeaderOffset != INVALID_OFFSET) {
+ if (mBuffer.position() > l2mtu) {
+ throw new IOException("IPv4 fragmentation is not supported");
+ }
+
ipHeaderOffset = mIpv4HeaderOffset;
// Populate the IPv4 totalLength field.
@@ -243,12 +287,15 @@
}
// [2] Finalize TCP or UDP header.
+ final int ipPayloadOffset;
if (mTcpHeaderOffset != INVALID_OFFSET) {
+ ipPayloadOffset = mTcpHeaderOffset;
// Populate the TCP header checksum field.
mBuffer.putShort(mTcpHeaderOffset + TCP_CHECKSUM_OFFSET, tcpChecksum(mBuffer,
ipHeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */,
mBuffer.position() - mTcpHeaderOffset /* transportLen */));
} else if (mUdpHeaderOffset != INVALID_OFFSET) {
+ ipPayloadOffset = mUdpHeaderOffset;
// Populate the UDP header length field.
mBuffer.putShort(mUdpHeaderOffset + UDP_LENGTH_OFFSET,
(short) (mBuffer.position() - mUdpHeaderOffset));
@@ -257,11 +304,81 @@
mBuffer.putShort(mUdpHeaderOffset + UDP_CHECKSUM_OFFSET, udpChecksum(mBuffer,
ipHeaderOffset /* ipOffset */, mUdpHeaderOffset /* transportOffset */));
} else {
- throw new IOException("Packet is missing neither TCP nor UDP header");
+ throw new IOException("Packet has neither TCP nor UDP header");
}
- mBuffer.flip();
- return mBuffer;
+ if (mBuffer.position() <= l2mtu) {
+ mBuffer.flip();
+ return Arrays.asList(mBuffer);
+ }
+
+ // IPv6 Packet is fragmented into multiple smaller packets that would fit within the link
+ // MTU.
+ // Refer to https://tools.ietf.org/html/rfc2460
+ //
+ // original packet:
+ // +------------------+--------------+--------------+--//--+----------+
+ // | Unfragmentable | first | second | | last |
+ // | Part | fragment | fragment | .... | fragment |
+ // +------------------+--------------+--------------+--//--+----------+
+ //
+ // fragment packets:
+ // +------------------+--------+--------------+
+ // | Unfragmentable |Fragment| first |
+ // | Part | Header | fragment |
+ // +------------------+--------+--------------+
+ //
+ // +------------------+--------+--------------+
+ // | Unfragmentable |Fragment| second |
+ // | Part | Header | fragment |
+ // +------------------+--------+--------------+
+ // o
+ // o
+ // o
+ // +------------------+--------+----------+
+ // | Unfragmentable |Fragment| last |
+ // | Part | Header | fragment |
+ // +------------------+--------+----------+
+ final List<ByteBuffer> fragments = new ArrayList<>();
+ final int totalPayloadLen = mBuffer.position() - ipPayloadOffset;
+ final int perPacketPayloadLen = l2mtu - ipPayloadOffset - IPV6_FRAGMENT_HEADER_LEN;
+ final short protocol = (short) Byte.toUnsignedInt(
+ mBuffer.get(mIpv6HeaderOffset + IPV6_PROTOCOL_OFFSET));
+ Random random = new Random();
+ final int id = random.nextInt(Integer.MAX_VALUE);
+ int startOffset = 0;
+ // Copy the packet content to a byte array.
+ byte[] packet = new byte[mBuffer.position()];
+ // The ByteBuffer#get(int index, byte[] dst) method is only available in API level 35 and
+ // above. Here, we use a more primitive approach: reposition the ByteBuffer to the beginning
+ // before copying, then return its position to the end afterward.
+ mBuffer.position(0);
+ mBuffer.get(packet);
+ mBuffer.position(packet.length);
+ while (startOffset < totalPayloadLen) {
+ int copyPayloadLen = Math.min(perPacketPayloadLen, totalPayloadLen - startOffset);
+ // The data portion must be broken into segments aligned with 8-octet boundaries.
+ // Therefore, the payload length should be a multiple of 8 bytes for all fragments
+ // except the last one.
+ // See https://datatracker.ietf.org/doc/html/rfc791 section 3.2
+ if (copyPayloadLen != totalPayloadLen - startOffset) {
+ copyPayloadLen &= ~7;
+ }
+ ByteBuffer fragment = ByteBuffer.allocate(ipPayloadOffset + IPV6_FRAGMENT_HEADER_LEN
+ + copyPayloadLen);
+ fragment.put(packet, 0, ipPayloadOffset);
+ writeFragmentHeader(fragment, protocol, startOffset,
+ startOffset + copyPayloadLen < totalPayloadLen, id);
+ fragment.put(packet, ipPayloadOffset + startOffset, copyPayloadLen);
+ fragment.putShort(mIpv6HeaderOffset + IPV6_LEN_OFFSET,
+ (short) (IPV6_FRAGMENT_HEADER_LEN + copyPayloadLen));
+ fragment.put(mIpv6HeaderOffset + IPV6_PROTOCOL_OFFSET, (byte) IPPROTO_FRAGMENT);
+ fragment.flip();
+ fragments.add(fragment);
+ startOffset += copyPayloadLen;
+ }
+
+ return fragments;
}
/**
diff --git a/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
new file mode 100644
index 0000000..cd6bfec
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.net.module.util;
+
+import android.os.Build;
+import android.system.ErrnoException;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.util.HashMap;
+import java.util.NoSuchElementException;
+
+/**
+ * Subclass of BpfMap for maps that are only ever written by one userspace writer.
+ *
+ * This class stores all map data in a userspace HashMap in addition to in the BPF map. This makes
+ * reads (but not iterations) much faster because they do not require a system call or converting
+ * the raw map read to the Value struct. See, e.g., b/343166906 .
+ *
+ * Users of this class must ensure that no BPF program ever writes to the map, and that all
+ * userspace writes to the map occur through this object. Other userspace code may still read from
+ * the map; only writes are required to go through this object.
+ *
+ * Reads and writes to this object are thread-safe and internally synchronized. The read and write
+ * methods are synchronized to ensure that current writers always result in a consistent internal
+ * state (without synchronization, two concurrent writes might update the underlying map and the
+ * cache in the opposite order, resulting in the cache being out of sync with the map).
+ *
+ * getNextKey and iteration over the map are not synchronized or cached and always access the
+ * isunderlying map. The values returned by these calls may be temporarily out of sync with the
+ * values read and written through this object.
+ *
+ * TODO: consider caching reads on iterations as well. This is not trivial because the semantics for
+ * iterating BPF maps require passing in the previously-returned key, and Java iterators only
+ * support iterating from the beginning. It could be done by implementing forEach and possibly by
+ * making getFirstKey and getNextKey private (if no callers are using them). Because HashMap is not
+ * thread-safe, implementing forEach would require either making that method synchronized (and
+ * block reads and updates from other threads until iteration is complete) or switching the
+ * internal HashMap to ConcurrentHashMap.
+ *
+ * @param <K> the key of the map.
+ * @param <V> the value of the map.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public class SingleWriterBpfMap<K extends Struct, V extends Struct> extends BpfMap<K, V> {
+ // HashMap instead of ArrayMap because it performs better on larger maps, and many maps used in
+ // our code can contain hundreds of items.
+ private final HashMap<K, V> mCache = new HashMap<>();
+
+ // This should only ever be called (hence private) once for a given 'path'.
+ // Java-wise what matters is the entire {path, key, value} triplet,
+ // but of course the kernel exclusive lock is just on the path (fd),
+ // and any BpfMap has (or should have...) well defined key/value types
+ // (or at least their sizes) so in practice it doesn't really matter.
+ private SingleWriterBpfMap(@NonNull final String path, final Class<K> key,
+ final Class<V> value) throws ErrnoException, NullPointerException {
+ super(path, BPF_F_RDWR_EXCLUSIVE, key, value);
+
+ // Populate cache with the current map contents.
+ K currentKey = super.getFirstKey();
+ while (currentKey != null) {
+ mCache.put(currentKey, super.getValue(currentKey));
+ currentKey = super.getNextKey(currentKey);
+ }
+ }
+
+ // This allows reuse of SingleWriterBpfMap objects for the same {path, keyClass, valueClass}.
+ // These are never destroyed, so once created the lock is (effectively) held till process death
+ // (even if fixed, there would still be a write-only fd cache in underlying BpfMap base class).
+ private static final HashMap<Pair<String, Pair<Class, Class>>, SingleWriterBpfMap>
+ singletonCache = new HashMap<>();
+
+ // This is the public 'factory method' that (creates if needed and) returns a singleton instance
+ // for a given map. This holds an exclusive lock and has a permanent write-through cache.
+ // It will not be released until process death (or at least unload of the relevant class loader)
+ public synchronized static <KK extends Struct, VV extends Struct> SingleWriterBpfMap<KK,VV>
+ getSingleton(@NonNull final String path, final Class<KK> key, final Class<VV> value)
+ throws ErrnoException, NullPointerException {
+ var cacheKey = new Pair<>(path, new Pair<Class,Class>(key, value));
+ if (!singletonCache.containsKey(cacheKey))
+ singletonCache.put(cacheKey, new SingleWriterBpfMap(path, key, value));
+ return singletonCache.get(cacheKey);
+ }
+
+ @Override
+ public synchronized void updateEntry(K key, V value) throws ErrnoException {
+ super.updateEntry(key, value);
+ mCache.put(key, value);
+ }
+
+ @Override
+ public synchronized void insertEntry(K key, V value)
+ throws ErrnoException, IllegalStateException {
+ super.insertEntry(key, value);
+ mCache.put(key, value);
+ }
+
+ @Override
+ public synchronized void replaceEntry(K key, V value)
+ throws ErrnoException, NoSuchElementException {
+ super.replaceEntry(key, value);
+ mCache.put(key, value);
+ }
+
+ @Override
+ public synchronized boolean insertOrReplaceEntry(K key, V value) throws ErrnoException {
+ final boolean ret = super.insertOrReplaceEntry(key, value);
+ mCache.put(key, value);
+ return ret;
+ }
+
+ @Override
+ public synchronized boolean deleteEntry(K key) throws ErrnoException {
+ final boolean ret = super.deleteEntry(key);
+ mCache.remove(key);
+ return ret;
+ }
+
+ @Override
+ public synchronized boolean containsKey(@NonNull K key) throws ErrnoException {
+ return mCache.containsKey(key);
+ }
+
+ @Override
+ public synchronized V getValue(@NonNull K key) throws ErrnoException {
+ return mCache.get(key);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/SocketUtils.java b/staticlibs/device/com/android/net/module/util/SocketUtils.java
index 5e6a6c6..51671a6 100644
--- a/staticlibs/device/com/android/net/module/util/SocketUtils.java
+++ b/staticlibs/device/com/android/net/module/util/SocketUtils.java
@@ -19,8 +19,7 @@
import static android.net.util.SocketUtils.closeSocket;
import android.annotation.NonNull;
-import android.annotation.RequiresApi;
-import android.os.Build;
+import android.annotation.SuppressLint;
import android.system.NetlinkSocketAddress;
import java.io.FileDescriptor;
@@ -41,7 +40,11 @@
/**
* Make a socket address to communicate with netlink.
*/
- @NonNull @RequiresApi(Build.VERSION_CODES.S)
+ // NetlinkSocketAddress was CorePlatformApi on R and linter warns this is available on S+.
+ // android.net.util.SocketUtils.makeNetlinkSocketAddress can be used instead, but this method
+ // has been used on R, so suppress the linter and keep as it is.
+ @SuppressLint("NewApi")
+ @NonNull
public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) {
return new NetlinkSocketAddress(portId, groupsMask);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java b/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
similarity index 96%
rename from Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java
rename to staticlibs/device/com/android/net/module/util/SyncStateMachine.java
index a17eb26..da184d3 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java
+++ b/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.networkstack.tethering.util;
+package com.android.net.module.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -165,6 +165,11 @@
* The message is processed sequentially, so calling this method recursively is not permitted.
* In other words, using this method inside State#enter, State#exit, or State#processMessage
* is incorrect and will result in an IllegalStateException.
+ *
+ * @param what is assigned to Message.what
+ * @param arg1 is assigned to Message.arg1
+ * @param arg2 is assigned to Message.arg2
+ * @param obj is assigned to Message.obj
*/
public final void processMessage(int what, int arg1, int arg2, @Nullable Object obj) {
ensureCorrectThread();
@@ -189,6 +194,15 @@
mCurrentlyProcessing = Integer.MIN_VALUE;
}
+ /**
+ * Synchronously process a message and perform state transition.
+ *
+ * @param what is assigned to Message.what.
+ */
+ public final void processMessage(int what) {
+ processMessage(what, 0, 0, null);
+ }
+
private void maybeProcessSelfMessageQueue() {
while (!mSelfMsgQueue.isEmpty()) {
currentStateProcessMessageThenPerformTransitions(mSelfMsgQueue.poll());
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
index 92ec0c4..df7010e 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -41,6 +41,10 @@
public static final short IFLA_ADDRESS = 1;
public static final short IFLA_IFNAME = 3;
public static final short IFLA_MTU = 4;
+ public static final short IFLA_INET6_ADDR_GEN_MODE = 8;
+ public static final short IFLA_AF_SPEC = 26;
+
+ public static final short IN6_ADDR_GEN_MODE_NONE = 1;
private int mMtu;
@NonNull
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
index 02d1574..28eeaea 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
@@ -49,7 +49,7 @@
@Field(order = 4, type = Type.U32)
public final long change;
- StructIfinfoMsg(short family, int type, int index, long flags, long change) {
+ public StructIfinfoMsg(short family, int type, int index, long flags, long change) {
this.family = family;
this.type = type;
this.index = index;
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 19d8bbe..a8c50d8 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -129,7 +129,9 @@
public static final int IPV6_PROTOCOL_OFFSET = 6;
public static final int IPV6_SRC_ADDR_OFFSET = 8;
public static final int IPV6_DST_ADDR_OFFSET = 24;
+ public static final int IPV6_FRAGMENT_ID_OFFSET = 4;
public static final int IPV6_MIN_MTU = 1280;
+ public static final int IPV6_FRAGMENT_ID_LEN = 4;
public static final int IPV6_FRAGMENT_HEADER_LEN = 8;
public static final int RFC7421_PREFIX_LENGTH = 64;
// getSockOpt() for v6 MTU
@@ -141,6 +143,8 @@
public static final Inet6Address IPV6_ADDR_ALL_HOSTS_MULTICAST =
(Inet6Address) InetAddresses.parseNumericAddress("ff02::3");
+ public static final int IPPROTO_FRAGMENT = 44;
+
/**
* ICMP constants.
*
@@ -180,6 +184,7 @@
public static final int ICMPV6_NS_HEADER_LEN = 24;
public static final int ICMPV6_NA_HEADER_LEN = 24;
public static final int ICMPV6_ND_OPTION_TLLA_LEN = 8;
+ public static final int ICMPV6_ND_OPTION_SLLA_LEN = 8;
public static final int NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER = 1 << 31;
public static final int NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED = 1 << 30;
diff --git a/staticlibs/lint-baseline.xml b/staticlibs/lint-baseline.xml
deleted file mode 100644
index 2ee3a43..0000000
--- a/staticlibs/lint-baseline.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha04" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha04">
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `makeNetlinkSocketAddress`"
- errorLine1=" Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Connectivity/staticlibs/device/com/android/net/module/util/ip/NetlinkMonitor.java"
- line="111"
- column="25"/>
- </issue>
-
-</issues>
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
index d716358..cd51004 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
@@ -151,7 +151,7 @@
inline base::Result<void> BpfRingbufBase::Init(const char* path) {
- mRingFd.reset(mapRetrieveRW(path));
+ mRingFd.reset(mapRetrieveExclusiveRW(path));
if (!mRingFd.ok()) {
return android::base::ErrnoError()
<< "failed to retrieve ringbuffer at " << path;
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index dc7925e..4ddec8b 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -39,11 +39,12 @@
// Android U / 14 (api level 34) - various new program types added
#define BPFLOADER_U_VERSION 38u
-// Android V / 15 (api level 35) - platform only
+// Android U QPR2 / 14 (api level 34) - platform only
// (note: the platform bpfloader in V isn't really versioned at all,
// as there is no need as it can only load objects compiled at the
// same time as itself and the rest of the platform)
-#define BPFLOADER_PLATFORM_VERSION 41u
+#define BPFLOADER_U_QPR2_VERSION 41u
+#define BPFLOADER_PLATFORM_VERSION BPFLOADER_U_QPR2_VERSION
// Android Mainline - this bpfloader should eventually go back to T (or even S)
// Note: this value (and the following +1u's) are hardcoded in NetBpfLoad.cpp
@@ -55,8 +56,11 @@
// Android Mainline BpfLoader when running on Android U
#define BPFLOADER_MAINLINE_U_VERSION (BPFLOADER_MAINLINE_T_VERSION + 1u)
+// Android Mainline BpfLoader when running on Android U QPR3
+#define BPFLOADER_MAINLINE_U_QPR3_VERSION (BPFLOADER_MAINLINE_U_VERSION + 1u)
+
// Android Mainline BpfLoader when running on Android V
-#define BPFLOADER_MAINLINE_V_VERSION (BPFLOADER_MAINLINE_U_VERSION + 1u)
+#define BPFLOADER_MAINLINE_V_VERSION (BPFLOADER_MAINLINE_U_QPR3_VERSION + 1u)
/* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER
* before #include "bpf_helpers.h" to change which bpfloaders will
@@ -133,6 +137,7 @@
#define KVER_5_4 KVER(5, 4, 0)
#define KVER_5_8 KVER(5, 8, 0)
#define KVER_5_9 KVER(5, 9, 0)
+#define KVER_5_10 KVER(5, 10, 0)
#define KVER_5_15 KVER(5, 15, 0)
#define KVER_6_1 KVER(6, 1, 0)
#define KVER_6_6 KVER(6, 6, 0)
diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
index cb02de8..73cef89 100644
--- a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
@@ -16,8 +16,11 @@
#pragma once
+#include <stdlib.h>
+#include <unistd.h>
#include <linux/bpf.h>
#include <linux/unistd.h>
+#include <sys/file.h>
#ifdef BPF_FD_JUST_USE_INT
#define BPF_FD_TYPE int
@@ -128,20 +131,61 @@
});
}
-inline int mapRetrieve(const char* pathname, uint32_t flag) {
- return bpfFdGet(pathname, flag);
+int bpfGetFdMapId(const BPF_FD_TYPE map_fd);
+
+inline int bpfLock(int fd, short type) {
+ if (fd < 0) return fd; // pass any errors straight through
+#ifdef BPF_MAP_LOCKLESS_FOR_TEST
+ return fd;
+#endif
+#ifdef BPF_FD_JUST_USE_INT
+ int mapId = bpfGetFdMapId(fd);
+ int saved_errno = errno;
+#else
+ base::unique_fd ufd(fd);
+ int mapId = bpfGetFdMapId(ufd);
+ int saved_errno = errno;
+ (void)ufd.release();
+#endif
+ // 4.14+ required to fetch map id, but we don't want to call isAtLeastKernelVersion
+ if (mapId == -1 && saved_errno == EINVAL) return fd;
+ if (mapId <= 0) abort(); // should not be possible
+
+ // on __LP64__ (aka. 64-bit userspace) 'struct flock64' is the same as 'struct flock'
+ struct flock64 fl = {
+ .l_type = type, // short: F_{RD,WR,UN}LCK
+ .l_whence = SEEK_SET, // short: SEEK_{SET,CUR,END}
+ .l_start = mapId, // off_t: start offset
+ .l_len = 1, // off_t: number of bytes
+ };
+
+ // see: bionic/libc/bionic/fcntl.cpp: iff !__LP64__ this uses fcntl64
+ int ret = fcntl(fd, F_OFD_SETLK, &fl);
+ if (!ret) return fd; // success
+ close(fd);
+ return ret; // most likely -1 with errno == EAGAIN, due to already held lock
+}
+
+inline int mapRetrieveLocklessRW(const char* pathname) {
+ return bpfFdGet(pathname, 0);
+}
+
+inline int mapRetrieveExclusiveRW(const char* pathname) {
+ return bpfLock(mapRetrieveLocklessRW(pathname), F_WRLCK);
}
inline int mapRetrieveRW(const char* pathname) {
- return mapRetrieve(pathname, 0);
+ return bpfLock(mapRetrieveLocklessRW(pathname), F_RDLCK);
}
inline int mapRetrieveRO(const char* pathname) {
- return mapRetrieve(pathname, BPF_F_RDONLY);
+ return bpfFdGet(pathname, BPF_F_RDONLY);
}
+// WARNING: it's impossible to grab a shared (ie. read) lock on a write-only fd,
+// so we instead choose to grab an exclusive (ie. write) lock.
inline int mapRetrieveWO(const char* pathname) {
- return mapRetrieve(pathname, BPF_F_WRONLY);
+ return bpfLock(bpfFdGet(pathname, BPF_F_WRONLY), F_WRLCK);
}
inline int retrieveProgram(const char* pathname) {
diff --git a/staticlibs/native/bpfmapjni/Android.bp b/staticlibs/native/bpfmapjni/Android.bp
index 7e6b4ec..969ebd4 100644
--- a/staticlibs/native/bpfmapjni/Android.bp
+++ b/staticlibs/native/bpfmapjni/Android.bp
@@ -39,7 +39,7 @@
"-Werror",
"-Wno-unused-parameter",
],
- sdk_version: "30",
+ sdk_version: "current",
min_sdk_version: "30",
apex_available: [
"com.android.tethering",
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
index b92f107..1923ceb 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
@@ -35,7 +35,24 @@
jstring path, jint mode, jint keySize, jint valueSize) {
ScopedUtfChars pathname(env, path);
- jint fd = bpf::bpfFdGet(pathname.c_str(), static_cast<unsigned>(mode));
+ jint fd = -1;
+ switch (mode) {
+ case 0:
+ fd = bpf::mapRetrieveRW(pathname.c_str());
+ break;
+ case BPF_F_RDONLY:
+ fd = bpf::mapRetrieveRO(pathname.c_str());
+ break;
+ case BPF_F_WRONLY:
+ fd = bpf::mapRetrieveWO(pathname.c_str());
+ break;
+ case BPF_F_RDONLY|BPF_F_WRONLY:
+ fd = bpf::mapRetrieveExclusiveRW(pathname.c_str());
+ break;
+ default:
+ errno = EINVAL;
+ break;
+ }
if (fd < 0) {
jniThrowErrnoException(env, "nativeBpfFdGet", errno);
diff --git a/staticlibs/native/tcutils/Android.bp b/staticlibs/native/tcutils/Android.bp
index 926590d..e4742ce 100644
--- a/staticlibs/native/tcutils/Android.bp
+++ b/staticlibs/native/tcutils/Android.bp
@@ -21,7 +21,10 @@
name: "libtcutils",
srcs: ["tcutils.cpp"],
export_include_dirs: ["include"],
- header_libs: ["bpf_headers"],
+ header_libs: [
+ "bpf_headers",
+ "libbase_headers",
+ ],
shared_libs: [
"liblog",
],
@@ -31,7 +34,7 @@
"-Werror",
"-Wno-unused-parameter",
],
- sdk_version: "30",
+ sdk_version: "current",
min_sdk_version: "30",
apex_available: [
"com.android.tethering",
diff --git a/staticlibs/native/tcutils/scopeguard.h b/staticlibs/native/tcutils/scopeguard.h
deleted file mode 100644
index 76bbb93..0000000
--- a/staticlibs/native/tcutils/scopeguard.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// -----------------------------------------------------------------------------
-// TODO: figure out a way to use libbase_ndk. This is currently not working
-// because of missing apex availability. For now, we can use a copy of
-// ScopeGuard which is very lean compared to unique_fd. This code has been
-// copied verbatim from:
-// https://cs.android.com/android/platform/superproject/+/master:system/libbase/include/android-base/scopeguard.h
-
-#pragma once
-
-#include <utility> // for std::move, std::forward
-
-namespace android {
-namespace base {
-
-// ScopeGuard ensures that the specified functor is executed no matter how the
-// current scope exits.
-template <typename F> class ScopeGuard {
-public:
- ScopeGuard(F &&f) : f_(std::forward<F>(f)), active_(true) {}
-
- ScopeGuard(ScopeGuard &&that) noexcept
- : f_(std::move(that.f_)), active_(that.active_) {
- that.active_ = false;
- }
-
- template <typename Functor>
- ScopeGuard(ScopeGuard<Functor> &&that)
- : f_(std::move(that.f_)), active_(that.active_) {
- that.active_ = false;
- }
-
- ~ScopeGuard() {
- if (active_)
- f_();
- }
-
- ScopeGuard() = delete;
- ScopeGuard(const ScopeGuard &) = delete;
- void operator=(const ScopeGuard &) = delete;
- void operator=(ScopeGuard &&that) = delete;
-
- void Disable() { active_ = false; }
-
- bool active() const { return active_; }
-
-private:
- template <typename Functor> friend class ScopeGuard;
-
- F f_;
- bool active_;
-};
-
-template <typename F> ScopeGuard<F> make_scope_guard(F &&f) {
- return ScopeGuard<F>(std::forward<F>(f));
-}
-
-} // namespace base
-} // namespace android
diff --git a/staticlibs/native/tcutils/tcutils.cpp b/staticlibs/native/tcutils/tcutils.cpp
index c82390f..21e781c 100644
--- a/staticlibs/native/tcutils/tcutils.cpp
+++ b/staticlibs/native/tcutils/tcutils.cpp
@@ -20,8 +20,10 @@
#include "logging.h"
#include "bpf/KernelUtils.h"
-#include "scopeguard.h"
+#include <BpfSyscallWrappers.h>
+#include <android-base/scopeguard.h>
+#include <android-base/unique_fd.h>
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
@@ -39,10 +41,6 @@
#include <unistd.h>
#include <utility>
-#define BPF_FD_JUST_USE_INT
-#include <BpfSyscallWrappers.h>
-#undef BPF_FD_JUST_USE_INT
-
// The maximum length of TCA_BPF_NAME. Sync from net/sched/cls_bpf.c.
#define CLS_BPF_NAME_LEN 256
@@ -52,6 +50,9 @@
namespace android {
namespace {
+using base::make_scope_guard;
+using base::unique_fd;
+
/**
* IngressPoliceFilterBuilder builds a nlmsg request equivalent to the following
* tc command:
@@ -130,7 +131,7 @@
// class members
const unsigned mBurstInBytes;
const char *mBpfProgPath;
- int mBpfFd;
+ unique_fd mBpfFd;
Request mRequest;
static double getTickInUsec() {
@@ -139,7 +140,7 @@
ALOGE("fopen(\"/proc/net/psched\"): %s", strerror(errno));
return 0.0;
}
- auto scopeGuard = base::make_scope_guard([fp] { fclose(fp); });
+ auto scopeGuard = make_scope_guard([fp] { fclose(fp); });
uint32_t t2us;
uint32_t us2t;
@@ -166,7 +167,6 @@
unsigned burstInBytes, const char* bpfProgPath)
: mBurstInBytes(burstInBytes),
mBpfProgPath(bpfProgPath),
- mBpfFd(-1),
mRequest{
.n = {
.nlmsg_len = sizeof(mRequest),
@@ -298,13 +298,6 @@
}
// clang-format on
- ~IngressPoliceFilterBuilder() {
- // TODO: use unique_fd
- if (mBpfFd != -1) {
- close(mBpfFd);
- }
- }
-
constexpr unsigned getRequestSize() const { return sizeof(Request); }
private:
@@ -332,14 +325,14 @@
}
int initBpfFd() {
- mBpfFd = bpf::retrieveProgram(mBpfProgPath);
- if (mBpfFd == -1) {
+ mBpfFd.reset(bpf::retrieveProgram(mBpfProgPath));
+ if (!mBpfFd.ok()) {
int error = errno;
ALOGE("retrieveProgram failed: %d", error);
return -error;
}
- mRequest.opt.acts.act2.opt.fd.u32 = static_cast<uint32_t>(mBpfFd);
+ mRequest.opt.acts.act2.opt.fd.u32 = static_cast<uint32_t>(mBpfFd.get());
snprintf(mRequest.opt.acts.act2.opt.name.str,
sizeof(mRequest.opt.acts.act2.opt.name.str), "%s:[*fsobj]",
basename(mBpfProgPath));
@@ -370,14 +363,13 @@
int sendAndProcessNetlinkResponse(const void *req, int len) {
// TODO: use unique_fd instead of ScopeGuard
- int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
- if (fd == -1) {
+ unique_fd fd(socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE));
+ if (!fd.ok()) {
int error = errno;
ALOGE("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE): %d",
error);
return -error;
}
- auto scopeGuard = base::make_scope_guard([fd] { close(fd); });
static constexpr int on = 1;
if (setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on))) {
@@ -460,10 +452,9 @@
}
int hardwareAddressType(const char *interface) {
- int fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
- if (fd < 0)
+ unique_fd fd(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+ if (!fd.ok())
return -errno;
- auto scopeGuard = base::make_scope_guard([fd] { close(fd); });
struct ifreq ifr = {};
// We use strncpy() instead of strlcpy() since kernel has to be able
@@ -576,12 +567,11 @@
// /sys/fs/bpf/... direct-action
int tcAddBpfFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto,
const char *bpfProgPath) {
- const int bpfFd = bpf::retrieveProgram(bpfProgPath);
- if (bpfFd == -1) {
+ unique_fd bpfFd(bpf::retrieveProgram(bpfProgPath));
+ if (!bpfFd.ok()) {
ALOGE("retrieveProgram failed: %d", errno);
return -errno;
}
- auto scopeGuard = base::make_scope_guard([bpfFd] { close(bpfFd); });
struct {
nlmsghdr n;
diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp
index 59ef20d..44abba2 100644
--- a/staticlibs/netd/Android.bp
+++ b/staticlibs/netd/Android.bp
@@ -22,7 +22,7 @@
sdk_version: "system_current",
min_sdk_version: "30",
static_libs: [
- "netd_aidl_interface-V14-java",
+ "netd_aidl_interface-V15-java",
],
apex_available: [
"//apex_available:platform", // used from services.net
@@ -45,7 +45,7 @@
cc_library_static {
name: "netd_aidl_interface-lateststable-ndk",
whole_static_libs: [
- "netd_aidl_interface-V14-ndk",
+ "netd_aidl_interface-V15-ndk",
],
apex_available: [
"com.android.resolv",
@@ -56,12 +56,12 @@
cc_defaults {
name: "netd_aidl_interface_lateststable_cpp_static",
- static_libs: ["netd_aidl_interface-V14-cpp"],
+ static_libs: ["netd_aidl_interface-V15-cpp"],
}
cc_defaults {
name: "netd_aidl_interface_lateststable_cpp_shared",
- shared_libs: ["netd_aidl_interface-V14-cpp"],
+ shared_libs: ["netd_aidl_interface-V15-cpp"],
}
aidl_interface {
@@ -167,6 +167,10 @@
version: "14",
imports: [],
},
+ {
+ version: "15",
+ imports: [],
+ },
],
frozen: true,
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/15/.hash
new file mode 100644
index 0000000..afdadcc
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/.hash
@@ -0,0 +1 @@
+2be6ff6fb01645cdddb3bb60f6de5727e5733267
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetd.aidl
new file mode 100644
index 0000000..80b3b62
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetd.aidl
@@ -0,0 +1,260 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+ boolean isAlive();
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+ boolean bandwidthEnableDataSaver(boolean enable);
+ /**
+ * @deprecated use networkCreate() instead.
+ */
+ void networkCreatePhysical(int netId, int permission);
+ /**
+ * @deprecated use networkCreate() instead.
+ */
+ void networkCreateVpn(int netId, boolean secure);
+ void networkDestroy(int netId);
+ void networkAddInterface(int netId, in @utf8InCpp String iface);
+ void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+ void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+ void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+ void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+ void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+ boolean tetherApplyDnsInterfaces();
+ android.net.TetherStatsParcel[] tetherGetStats();
+ void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+ void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+ @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+ void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+ void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+ int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+ void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+ void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+ void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+ void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+ void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+ void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+ void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+ void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+ void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+ void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+ void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+ void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+ void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+ void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+ void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+ void strictUidCleartextPenalty(int uid, int policyPenalty);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+ */
+ @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+ */
+ void clatdStop(in @utf8InCpp String ifName);
+ boolean ipfwdEnabled();
+ @utf8InCpp String[] ipfwdGetRequesterList();
+ void ipfwdEnableForwarding(in @utf8InCpp String requester);
+ void ipfwdDisableForwarding(in @utf8InCpp String requester);
+ void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+ void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+ void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+ void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+ void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+ void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+ void bandwidthSetGlobalAlert(long bytes);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void bandwidthAddNaughtyApp(int uid);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void bandwidthRemoveNaughtyApp(int uid);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void bandwidthAddNiceApp(int uid);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void bandwidthRemoveNiceApp(int uid);
+ void tetherStart(in @utf8InCpp String[] dhcpRanges);
+ void tetherStop();
+ boolean tetherIsEnabled();
+ void tetherInterfaceAdd(in @utf8InCpp String ifName);
+ void tetherInterfaceRemove(in @utf8InCpp String ifName);
+ @utf8InCpp String[] tetherInterfaceList();
+ void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+ @utf8InCpp String[] tetherDnsList();
+ void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+ void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+ void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+ void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+ int networkGetDefault();
+ void networkSetDefault(int netId);
+ void networkClearDefault();
+ void networkSetPermissionForNetwork(int netId, int permission);
+ void networkSetPermissionForUser(int permission, in int[] uids);
+ void networkClearPermissionForUser(in int[] uids);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void trafficSetNetPermForUids(int permission, in int[] uids);
+ void networkSetProtectAllow(int uid);
+ void networkSetProtectDeny(int uid);
+ boolean networkCanProtect(int uid);
+ void firewallSetFirewallType(int firewalltype);
+ void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void firewallSetUidRule(int childChain, int uid, int firewallRule);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void firewallEnableChildChain(int childChain, boolean enable);
+ @utf8InCpp String[] interfaceGetList();
+ android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+ void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+ void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+ void interfaceClearAddrs(in @utf8InCpp String ifName);
+ void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+ void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+ void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+ void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+ void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+ void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void firewallRemoveUidInterfaceRules(in int[] uids);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void trafficSwapActiveStatsMap();
+ IBinder getOemNetd();
+ void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+ android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+ void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+ void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+ void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ android.net.TetherStatsParcel[] tetherOffloadGetStats();
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+ void networkCreate(in android.net.NativeNetworkConfig config);
+ void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+ void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+ void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo);
+ void setNetworkAllowlist(in android.net.netd.aidl.NativeUidRangeConfig[] allowedNetworks);
+ void networkAllowBypassVpnOnNetwork(boolean allow, int uid, int netId);
+ const int IPV4 = 4;
+ const int IPV6 = 6;
+ const int CONF = 1;
+ const int NEIGH = 2;
+ const String IPSEC_INTERFACE_PREFIX = "ipsec";
+ const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+ const int IPV6_ADDR_GEN_MODE_NONE = 1;
+ const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+ const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+ const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+ const int PENALTY_POLICY_ACCEPT = 1;
+ const int PENALTY_POLICY_LOG = 2;
+ const int PENALTY_POLICY_REJECT = 3;
+ const int CLAT_MARK = 0xdeadc1a7;
+ const int LOCAL_NET_ID = 99;
+ const int DUMMY_NET_ID = 51;
+ const int UNREACHABLE_NET_ID = 52;
+ const String NEXTHOP_NONE = "";
+ const String NEXTHOP_UNREACHABLE = "unreachable";
+ const String NEXTHOP_THROW = "throw";
+ const int PERMISSION_NONE = 0;
+ const int PERMISSION_NETWORK = 1;
+ const int PERMISSION_SYSTEM = 2;
+ const int NO_PERMISSIONS = 0;
+ const int PERMISSION_INTERNET = 4;
+ const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+ const int PERMISSION_UNINSTALLED = (-1) /* -1 */;
+ /**
+ * @deprecated use FIREWALL_ALLOWLIST.
+ */
+ const int FIREWALL_WHITELIST = 0;
+ const int FIREWALL_ALLOWLIST = 0;
+ /**
+ * @deprecated use FIREWALL_DENYLIST.
+ */
+ const int FIREWALL_BLACKLIST = 1;
+ const int FIREWALL_DENYLIST = 1;
+ const int FIREWALL_RULE_ALLOW = 1;
+ const int FIREWALL_RULE_DENY = 2;
+ const int FIREWALL_CHAIN_NONE = 0;
+ const int FIREWALL_CHAIN_DOZABLE = 1;
+ const int FIREWALL_CHAIN_STANDBY = 2;
+ const int FIREWALL_CHAIN_POWERSAVE = 3;
+ const int FIREWALL_CHAIN_RESTRICTED = 4;
+ const String IF_STATE_UP = "up";
+ const String IF_STATE_DOWN = "down";
+ const String IF_FLAG_BROADCAST = "broadcast";
+ const String IF_FLAG_LOOPBACK = "loopback";
+ const String IF_FLAG_POINTOPOINT = "point-to-point";
+ const String IF_FLAG_RUNNING = "running";
+ const String IF_FLAG_MULTICAST = "multicast";
+ const int IPSEC_DIRECTION_IN = 0;
+ const int IPSEC_DIRECTION_OUT = 1;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+ oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+ oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+ oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+ oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+ oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+ oneway void onInterfaceAdded(@utf8InCpp String ifName);
+ oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+ oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+ oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+ oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+ oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+ @utf8InCpp String ifName;
+ @utf8InCpp String hwAddr;
+ @utf8InCpp String ipv4Addr;
+ int prefixLength;
+ @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/IpSecMigrateInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/IpSecMigrateInfoParcel.aidl
new file mode 100644
index 0000000..975a261
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/IpSecMigrateInfoParcel.aidl
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaOnlyImmutable
+parcelable IpSecMigrateInfoParcel {
+ int requestId;
+ int selAddrFamily;
+ int direction;
+ @utf8InCpp String oldSourceAddress;
+ @utf8InCpp String oldDestinationAddress;
+ @utf8InCpp String newSourceAddress;
+ @utf8InCpp String newDestinationAddress;
+ int interfaceId;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+ int mark;
+ int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..77d814b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+ int netId;
+ android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL;
+ int permission;
+ boolean secure;
+ android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+ boolean excludeLocalRoutes = false;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..e77a143
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkType.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeNetworkType {
+ PHYSICAL = 0,
+ VIRTUAL = 1,
+ PHYSICAL_LOCAL = 2,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..8a8be83
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeVpnType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeVpnType {
+ SERVICE = 1,
+ PLATFORM = 2,
+ LEGACY = 3,
+ OEM = 4,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+ @utf8InCpp String destination;
+ @utf8InCpp String ifName;
+ @utf8InCpp String nextHop;
+ int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+ boolean usingLegacyDnsProxy;
+ @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+ int inputInterfaceIndex;
+ int outputInterfaceIndex;
+ byte[] destination;
+ int prefixLength;
+ byte[] srcL2Address;
+ byte[] dstL2Address;
+ int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+ @utf8InCpp String iface;
+ long rxBytes;
+ long rxPackets;
+ long txBytes;
+ long txPackets;
+ int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/UidRangeParcel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+ int start;
+ int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..9bb679f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/netd/aidl/NativeUidRangeConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.netd.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeUidRangeConfig {
+ int netId;
+ android.net.UidRangeParcel[] uidRanges;
+ int subPriority;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
index 8ccefb2..80b3b62 100644
--- a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
@@ -203,6 +203,7 @@
void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo);
void setNetworkAllowlist(in android.net.netd.aidl.NativeUidRangeConfig[] allowedNetworks);
+ void networkAllowBypassVpnOnNetwork(boolean allow, int uid, int netId);
const int IPV4 = 4;
const int IPV6 = 6;
const int CONF = 1;
diff --git a/staticlibs/netd/binder/android/net/INetd.aidl b/staticlibs/netd/binder/android/net/INetd.aidl
index ee27e84..e4c63b9 100644
--- a/staticlibs/netd/binder/android/net/INetd.aidl
+++ b/staticlibs/netd/binder/android/net/INetd.aidl
@@ -1446,4 +1446,27 @@
* - subPriority: unused
*/
void setNetworkAllowlist(in NativeUidRangeConfig[] allowedNetworks);
+
+ /**
+ * Allow the UID to explicitly select the given network even if it is subject to a VPN.
+ *
+ * Throws ServiceSpecificException with error code EEXISTS when trying to add a bypass rule that
+ * already exists, and ENOENT when trying to remove a bypass rule that does not exist.
+ *
+ * netId specific bypass rules can be combined and are allowed to overlap with global VPN
+ * exclusions (by calling networkSetProtectAllow / networkSetProtectDeny, or by setting netId to
+ * 0). Adding or removing global VPN bypass rules does not affect the netId specific rules and
+ * vice versa.
+ *
+ * Note that if netId is set to 0 (NETID_UNSET) this API is equivalent to
+ * networkSetProtectAllow} / #networkSetProtectDeny.
+ *
+ * @param allow whether to allow or disallow the operation.
+ * @param uid the UID
+ * @param netId the netId that the UID is allowed to select.
+ *
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ void networkAllowBypassVpnOnNetwork(boolean allow, int uid, int netId);
}
diff --git a/staticlibs/netd/libnetdutils/InternetAddresses.cpp b/staticlibs/netd/libnetdutils/InternetAddresses.cpp
index 322f1b1..6d98608 100644
--- a/staticlibs/netd/libnetdutils/InternetAddresses.cpp
+++ b/staticlibs/netd/libnetdutils/InternetAddresses.cpp
@@ -16,6 +16,7 @@
#include "netdutils/InternetAddresses.h"
+#include <stdlib.h>
#include <string>
#include <android-base/stringprintf.h>
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index fa466f8..cf67a82 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -39,6 +39,7 @@
"//packages/modules/NetworkStack/tests/integration",
],
lint: {
+ strict_updatability_linting: true,
test: true,
},
}
@@ -56,4 +57,7 @@
],
jarjar_rules: "jarjar-rules.txt",
test_suites: ["device-tests"],
+ lint: {
+ strict_updatability_linting: true,
+ },
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
index e40cd6b..886336c 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
@@ -21,9 +21,13 @@
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_FRAGMENT_ID_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_FRAGMENT_ID_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK;
import static com.android.net.module.util.NetworkStackConstants.TCP_HEADER_MIN_LEN;
import static com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN;
@@ -54,6 +58,8 @@
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -489,10 +495,103 @@
(byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
};
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_NO_FRAG =
+ new byte[] {
+ // packet = Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", type='IPv6')/
+ // IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, fl=0x515ca,
+ // hlim=0x40)/UDP(sport=9876, dport=433)/
+ // Raw([i%256 for i in range(0, 500)]);
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x86, (byte) 0xdd,
+ // IPv6 header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x01, (byte) 0xfc, (byte) 0x11, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x01, (byte) 0xfc, (byte) 0xd3, (byte) 0x9e,
+ // Data
+ // 500 bytes of repeated 0x00~0xff
+ };
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG1 =
+ new byte[] {
+ // packet = Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", type='IPv6')/
+ // IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, fl=0x515ca,
+ // hlim=0x40)/UDP(sport=9876, dport=433)/
+ // Raw([i%256 for i in range(0, 500)]);
+ // packets=fragment6(packet, 400);
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x86, (byte) 0xdd,
+ // IPv6 header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x01, (byte) 0x58, (byte) 0x2c, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // Fragement Header
+ (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x01, (byte) 0xfc, (byte) 0xd3, (byte) 0x9e,
+ // Data
+ // 328 bytes of repeated 0x00~0xff, start:0x00 end:0x47
+ };
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG2 =
+ new byte[] {
+ // packet = Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff", type='IPv6')/
+ // IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80, fl=0x515ca,
+ // hlim=0x40)/UDP(sport=9876, dport=433)/
+ // Raw([i%256 for i in range(0, 500)]);
+ // packets=fragment6(packet, 400);
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x86, (byte) 0xdd,
+ // IPv6 header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0xb4, (byte) 0x2c, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // Fragement Header
+ (byte) 0x11, (byte) 0x00, (byte) 0x01, (byte) 0x50,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // Data
+ // 172 bytes of repeated 0x00~0xff, start:0x48 end:0xf3
+ };
+
/**
* Build a packet which has ether header, IP header, TCP/UDP header and data.
* The ethernet header and data are optional. Note that both source mac address and
- * destination mac address are required for ethernet header.
+ * destination mac address are required for ethernet header. The packet will be fragmented into
+ * multiple smaller packets if the packet size exceeds L2 mtu.
*
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Layer 2 header (EthernetHeader) | (optional)
@@ -511,11 +610,12 @@
* @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
* currently supported.
* @param payload the payload.
+ * @param l2mtu the Link MTU. It's the upper bound of each packet size. Zero means no limit.
*/
@NonNull
- private ByteBuffer buildPacket(@Nullable final MacAddress srcMac,
+ private List<ByteBuffer> buildPackets(@Nullable final MacAddress srcMac,
@Nullable final MacAddress dstMac, final int l3proto, final int l4proto,
- @Nullable final ByteBuffer payload)
+ @Nullable final ByteBuffer payload, int l2mtu)
throws Exception {
if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
fail("Unsupported layer 3 protocol " + l3proto);
@@ -562,7 +662,15 @@
payload.clear();
}
- return packetBuilder.finalizePacket();
+ return packetBuilder.finalizePacket(l2mtu > 0 ? l2mtu : Integer.MAX_VALUE);
+ }
+
+ @NonNull
+ private ByteBuffer buildPacket(@Nullable final MacAddress srcMac,
+ @Nullable final MacAddress dstMac, final int l3proto, final int l4proto,
+ @Nullable final ByteBuffer payload)
+ throws Exception {
+ return buildPackets(srcMac, dstMac, l3proto, l4proto, payload, 0).get(0);
}
/**
@@ -874,6 +982,66 @@
assertArrayEquals(TEST_PACKET_IPV6HDR_UDPHDR_DATA, packet.array());
}
+ private void checkIpv6PacketIgnoreFragmentId(byte[] expected, byte[] actual) {
+ final int offset = ETHER_HEADER_LEN + IPV6_HEADER_LEN + IPV6_FRAGMENT_ID_OFFSET;
+ assertArrayEquals(Arrays.copyOf(expected, offset), Arrays.copyOf(actual, offset));
+ assertArrayEquals(
+ Arrays.copyOfRange(expected, offset + IPV6_FRAGMENT_ID_LEN, expected.length),
+ Arrays.copyOfRange(actual, offset + IPV6_FRAGMENT_ID_LEN, actual.length));
+ }
+
+ @Test
+ public void testBuildPacketIPv6FragmentUdpData() throws Exception {
+ // A UDP packet with 500 bytes payload will be fragmented into two UDP packets each carrying
+ // 328 and 172 bytes of payload if the Link MTU is 400. Note that only the first packet
+ // contains the original UDP header.
+ final int payloadLen = 500;
+ final int payloadLen1 = 328;
+ final int payloadLen2 = 172;
+ final int l2mtu = 400;
+ final byte[] payload = new byte[payloadLen];
+ // Initialize the payload with repeated values from 0x00 to 0xff.
+ for (int i = 0; i < payload.length; i++) {
+ payload[i] = (byte) (i & 0xff);
+ }
+
+ // Verify original UDP packet.
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP,
+ ByteBuffer.wrap(payload));
+ final int headerLen = TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_NO_FRAG.length;
+ assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_NO_FRAG,
+ Arrays.copyOf(packet.array(), headerLen));
+ assertArrayEquals(payload,
+ Arrays.copyOfRange(packet.array(), headerLen, headerLen + payloadLen));
+
+ // Verify fragments of UDP packet.
+ final List<ByteBuffer> packets = buildPackets(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP,
+ ByteBuffer.wrap(payload), l2mtu);
+ assertEquals(2, packets.size());
+
+ // Verify first fragment.
+ int headerLen1 = TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG1.length;
+ // (1) Compare packet content up to the UDP header, excluding the fragment ID as it's a
+ // random value.
+ checkIpv6PacketIgnoreFragmentId(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG1,
+ Arrays.copyOf(packets.get(0).array(), headerLen1));
+ // (2) Compare UDP payload.
+ assertArrayEquals(Arrays.copyOf(payload, payloadLen1),
+ Arrays.copyOfRange(packets.get(0).array(), headerLen1, headerLen1 + payloadLen1));
+
+ // Verify second fragment (similar to the first one).
+ int headerLen2 = TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG2.length;
+ checkIpv6PacketIgnoreFragmentId(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA_FRAG2,
+ Arrays.copyOf(packets.get(1).array(), headerLen2));
+ assertArrayEquals(Arrays.copyOfRange(payload, payloadLen1, payloadLen1 + payloadLen2),
+ Arrays.copyOfRange(packets.get(1).array(), headerLen2, headerLen2 + payloadLen2));
+ // Verify that the fragment IDs in the first and second fragments are the same.
+ final int offset = ETHER_HEADER_LEN + IPV6_HEADER_LEN + IPV6_FRAGMENT_ID_OFFSET;
+ assertArrayEquals(
+ Arrays.copyOfRange(packets.get(0).array(), offset, offset + IPV6_FRAGMENT_ID_LEN),
+ Arrays.copyOfRange(packets.get(1).array(), offset, offset + IPV6_FRAGMENT_ID_LEN));
+ }
+
@Test
public void testFinalizePacketWithoutIpv4Header() throws Exception {
final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */, IPPROTO_IP,
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/SyncStateMachineTest.kt
similarity index 98%
rename from Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt
rename to staticlibs/tests/unit/src/com/android/net/module/util/SyncStateMachineTest.kt
index 3a57fdd..d534054 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/SyncStateMachineTest.kt
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.networkstack.tethering.util
+package com.android.net.module.util
import android.os.Message
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.State
-import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo
+import com.android.net.module.util.SyncStateMachine.StateInfo
import java.util.ArrayDeque
import java.util.ArrayList
import kotlin.test.assertFailsWith
@@ -45,7 +45,7 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
-class SynStateMachineTest {
+class SyncStateMachineTest {
private val mState1 = spy(object : TestState(MSG_1) {})
private val mState2 = spy(object : TestState(MSG_2) {})
private val mState3 = spy(object : TestState(MSG_3) {})
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
index 4c3fde6..b5e3dff 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
@@ -69,11 +69,11 @@
}
@Test
- public void testGetValueAsIntger() {
+ public void testGetValueAsInteger() {
final StructNlAttr attr1 = new StructNlAttr(IFA_FLAGS, TEST_ADDR_FLAGS);
final Integer integer1 = attr1.getValueAsInteger();
final int int1 = attr1.getValueAsInt(0x08 /* default value */);
- assertEquals(integer1, new Integer(TEST_ADDR_FLAGS));
+ assertEquals(integer1, Integer.valueOf(TEST_ADDR_FLAGS));
assertEquals(int1, TEST_ADDR_FLAGS);
// Malformed attribute.
diff --git a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
index df6067d..e634f0e 100644
--- a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
+++ b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
@@ -34,17 +34,13 @@
private val connectUtil by lazy { ConnectUtil(context) }
@Test
- fun testCheckConnectivity() {
- checkWifiSetup()
- checkTelephonySetup()
- }
-
- private fun checkWifiSetup() {
+ fun testCheckWifiSetup() {
if (!pm.hasSystemFeature(FEATURE_WIFI)) return
connectUtil.ensureWifiValidated()
}
- private fun checkTelephonySetup() {
+ @Test
+ fun testCheckTelephonySetup() {
if (!pm.hasSystemFeature(FEATURE_TELEPHONY)) return
val tm = context.getSystemService(TelephonyManager::class.java)
?: fail("Could not get telephony service")
@@ -52,7 +48,7 @@
val commonError = "Check the test bench. To run the tests anyway for quick & dirty local " +
"testing, you can use atest X -- " +
"--test-arg com.android.testutils.ConnectivityTestTargetPreparer" +
- ":ignore-connectivity-check:true"
+ ":ignore-mobile-data-check:true"
// Do not use assertEquals: it outputs "expected X, was Y", which looks like a test failure
if (tm.simState == TelephonyManager.SIM_STATE_ABSENT) {
fail("The device has no SIM card inserted. $commonError")
diff --git a/tests/cts/net/src/android/net/cts/MdnsTestUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/MdnsTestUtils.kt
similarity index 99%
rename from tests/cts/net/src/android/net/cts/MdnsTestUtils.kt
rename to staticlibs/testutils/devicetests/com/android/testutils/MdnsTestUtils.kt
index 93cec9c..8b88224 100644
--- a/tests/cts/net/src/android/net/cts/MdnsTestUtils.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/MdnsTestUtils.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.net.cts
+package com.android.testutils
import android.net.DnsResolver
import android.net.Network
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
index 4185b05..d5e91c2 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
@@ -24,6 +24,7 @@
* A JUnit Rule that sets feature flags based on `@FeatureFlag` annotations.
*
* This rule enables dynamic control of feature flag states during testing.
+ * And restores the original values after performing tests.
*
* **Usage:**
* ```kotlin
@@ -31,6 +32,8 @@
* @get:Rule
* val setFeatureFlagsRule = SetFeatureFlagsRule(setFlagsMethod = (name, enabled) -> {
* // Custom handling code.
+ * }, (name) -> {
+ * // Custom getter code to retrieve the original values.
* })
*
* // ... test methods with @FeatureFlag annotations
@@ -41,7 +44,10 @@
* }
* ```
*/
-class SetFeatureFlagsRule(val setFlagsMethod: (name: String, enabled: Boolean) -> Unit) : TestRule {
+class SetFeatureFlagsRule(
+ val setFlagsMethod: (name: String, enabled: Boolean?) -> Unit,
+ val getFlagsMethod: (name: String) -> Boolean?
+) : TestRule {
/**
* This annotation marks a test method as requiring a specific feature flag to be configured.
*
@@ -69,13 +75,20 @@
FeatureFlag::class.java
)
+ val valuesToBeRestored = mutableMapOf<String, Boolean?>()
for (featureFlagAnnotation in featureFlagAnnotations) {
+ valuesToBeRestored[featureFlagAnnotation.name] =
+ getFlagsMethod(featureFlagAnnotation.name)
setFlagsMethod(featureFlagAnnotation.name, featureFlagAnnotation.enabled)
}
// Execute the test method, which includes methods annotated with
// @Before, @Test and @After.
base.evaluate()
+
+ valuesToBeRestored.forEach {
+ setFlagsMethod(it.key, it.value)
+ }
}
}
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index f76916a..66362d4 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -360,17 +360,29 @@
timeoutMs: Long = defaultTimeoutMs,
errorMsg: String? = null,
test: (T) -> Boolean = { true }
- ) = (poll(timeoutMs) ?: fail("Did not receive ${T::class.simpleName} after ${timeoutMs}ms"))
+ ) = (poll(timeoutMs) ?: failWithErrorReason(errorMsg,
+ "Did not receive ${T::class.simpleName} after ${timeoutMs}ms"))
.also {
- if (it !is T) fail("Expected callback ${T::class.simpleName}, got $it")
+ if (it !is T) {
+ failWithErrorReason(
+ errorMsg,
+ "Expected callback ${T::class.simpleName}, got $it"
+ )
+ }
if (ANY_NETWORK !== network && it.network != network) {
- fail("Expected network $network for callback : $it")
+ failWithErrorReason(errorMsg, "Expected network $network for callback : $it")
}
if (!test(it)) {
- fail("${errorMsg ?: "Callback doesn't match predicate"} : $it")
+ failWithErrorReason(errorMsg, "Callback doesn't match predicate : $it")
}
} as T
+ // "Nothing" is the return type to declare a function never returns a value.
+ fun failWithErrorReason(errorMsg: String?, errorReason: String): Nothing {
+ val message = if (errorMsg != null) "$errorMsg : $errorReason" else errorReason
+ fail(message)
+ }
+
inline fun <reified T : CallbackEntry> expect(
network: HasNetwork,
timeoutMs: Long = defaultTimeoutMs,
diff --git a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
index 600a623..435fdd8 100644
--- a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
+++ b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
@@ -28,9 +28,11 @@
private const val CONNECTIVITY_CHECKER_APK = "ConnectivityTestPreparer.apk"
private const val CONNECTIVITY_PKG_NAME = "com.android.testutils.connectivitypreparer"
private const val CONNECTIVITY_CHECK_CLASS = "$CONNECTIVITY_PKG_NAME.ConnectivityCheckTest"
+
// As per the <instrumentation> defined in the checker manifest
private const val CONNECTIVITY_CHECK_RUNNER_NAME = "androidx.test.runner.AndroidJUnitRunner"
-private const val IGNORE_CONN_CHECK_OPTION = "ignore-connectivity-check"
+private const val IGNORE_WIFI_CHECK = "ignore-wifi-check"
+private const val IGNORE_MOBILE_DATA_CHECK = "ignore-mobile-data-check"
// The default updater package names, which might be updating packages while the CTS
// are running
@@ -41,14 +43,23 @@
*
* For quick and dirty local testing, the connectivity check can be disabled by running tests with
* "atest -- \
- * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-connectivity-check:true".
+ * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-mobile-data-check:true". \
+ * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-wifi-check:true".
*/
open class ConnectivityTestTargetPreparer : BaseTargetPreparer() {
private val installer = SuiteApkInstaller()
- @Option(name = IGNORE_CONN_CHECK_OPTION,
- description = "Disables the check for mobile data and wifi")
- private var ignoreConnectivityCheck = false
+ @Option(
+ name = IGNORE_WIFI_CHECK,
+ description = "Disables the check for wifi"
+ )
+ private var ignoreWifiCheck = false
+ @Option(
+ name = IGNORE_MOBILE_DATA_CHECK,
+ description = "Disables the check for mobile data"
+ )
+ private var ignoreMobileDataCheck = false
+
// The default value is never used, but false is a reasonable default
private var originalTestChainEnabled = false
private val originalUpdaterPkgsStatus = HashMap<String, Boolean>()
@@ -58,43 +69,62 @@
disableGmsUpdate(testInfo)
originalTestChainEnabled = getTestChainEnabled(testInfo)
originalUpdaterPkgsStatus.putAll(getUpdaterPkgsStatus(testInfo))
- setUpdaterNetworkingEnabled(testInfo, enableChain = true,
- enablePkgs = UPDATER_PKGS.associateWith { false })
- runPreparerApk(testInfo)
+ setUpdaterNetworkingEnabled(
+ testInfo,
+ enableChain = true,
+ enablePkgs = UPDATER_PKGS.associateWith { false }
+ )
+ runConnectivityCheckApk(testInfo)
+ refreshTime(testInfo)
}
- private fun runPreparerApk(testInfo: TestInformation) {
+ private fun runConnectivityCheckApk(testInfo: TestInformation) {
installer.setCleanApk(true)
installer.addTestFileName(CONNECTIVITY_CHECKER_APK)
installer.setShouldGrantPermission(true)
installer.setUp(testInfo)
+ val testMethods = mutableListOf<String>()
+ if (!ignoreWifiCheck) {
+ testMethods.add("testCheckWifiSetup")
+ }
+ if (!ignoreMobileDataCheck) {
+ testMethods.add("testCheckTelephonySetup")
+ }
+
+ testMethods.forEach {
+ runTestMethod(testInfo, it)
+ }
+ }
+
+ private fun runTestMethod(testInfo: TestInformation, method: String) {
val runner = DefaultRemoteAndroidTestRunner(
- CONNECTIVITY_PKG_NAME,
- CONNECTIVITY_CHECK_RUNNER_NAME,
- testInfo.device.iDevice)
+ CONNECTIVITY_PKG_NAME,
+ CONNECTIVITY_CHECK_RUNNER_NAME,
+ testInfo.device.iDevice
+ )
runner.runOptions = "--no-hidden-api-checks"
+ runner.setMethodName(CONNECTIVITY_CHECK_CLASS, method)
val receiver = CollectingTestListener()
if (!testInfo.device.runInstrumentationTests(runner, receiver)) {
- throw TargetSetupError("Device state check failed to complete",
- testInfo.device.deviceDescriptor)
+ throw TargetSetupError(
+ "Device state check failed to complete",
+ testInfo.device.deviceDescriptor
+ )
}
val runResult = receiver.currentRunResults
if (runResult.isRunFailure) {
- throw TargetSetupError("Failed to check device state before the test: " +
- runResult.runFailureMessage, testInfo.device.deviceDescriptor)
- }
-
- val ignoredTestClasses = mutableSetOf<String>()
- if (ignoreConnectivityCheck) {
- ignoredTestClasses.add(CONNECTIVITY_CHECK_CLASS)
+ throw TargetSetupError(
+ "Failed to check device state before the test: " +
+ runResult.runFailureMessage,
+ testInfo.device.deviceDescriptor
+ )
}
val errorMsg = runResult.testResults.mapNotNull { (testDescription, testResult) ->
- if (TestResult.TestStatus.FAILURE != testResult.status ||
- ignoredTestClasses.contains(testDescription.className)) {
+ if (TestResult.TestStatus.FAILURE != testResult.status) {
null
} else {
"$testDescription: ${testResult.stackTrace}"
@@ -102,21 +132,27 @@
}.joinToString("\n")
if (errorMsg.isBlank()) return
- throw TargetSetupError("Device setup checks failed. Check the test bench: \n$errorMsg",
- testInfo.device.deviceDescriptor)
+ throw TargetSetupError(
+ "Device setup checks failed. Check the test bench: \n$errorMsg",
+ testInfo.device.deviceDescriptor
+ )
}
private fun disableGmsUpdate(testInfo: TestInformation) {
// This will be a no-op on devices without root (su) or not using gservices, but that's OK.
- testInfo.exec("su 0 am broadcast " +
+ testInfo.exec(
+ "su 0 am broadcast " +
"-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
- "-e finsky.play_services_auto_update_enabled false")
+ "-e finsky.play_services_auto_update_enabled false"
+ )
}
private fun clearGmsUpdateOverride(testInfo: TestInformation) {
- testInfo.exec("su 0 am broadcast " +
+ testInfo.exec(
+ "su 0 am broadcast " +
"-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
- "--esn finsky.play_services_auto_update_enabled")
+ "--esn finsky.play_services_auto_update_enabled"
+ )
}
private fun setUpdaterNetworkingEnabled(
@@ -136,17 +172,27 @@
testInfo.exec("cmd connectivity get-chain3-enabled").contains("chain:enabled")
private fun getUpdaterPkgsStatus(testInfo: TestInformation) =
- UPDATER_PKGS.associateWith { pkg ->
- !testInfo.exec("cmd connectivity get-package-networking-enabled $pkg")
- .contains(":deny")
- }
+ UPDATER_PKGS.associateWith { pkg ->
+ !testInfo.exec("cmd connectivity get-package-networking-enabled $pkg")
+ .contains(":deny")
+ }
+
+ private fun refreshTime(testInfo: TestInformation,) {
+ // Forces a synchronous time refresh using the network. Time is fetched synchronously but
+ // this does not guarantee that system time is updated when it returns.
+ // This avoids flakes where the system clock rolls back, for example when using test
+ // settings like test_url_expiration_time in NetworkMonitor.
+ testInfo.exec("cmd network_time_update_service force_refresh")
+ }
override fun tearDown(testInfo: TestInformation, e: Throwable?) {
if (isTearDownDisabled) return
installer.tearDown(testInfo, e)
- setUpdaterNetworkingEnabled(testInfo,
- enableChain = originalTestChainEnabled,
- enablePkgs = originalUpdaterPkgsStatus)
+ setUpdaterNetworkingEnabled(
+ testInfo,
+ enableChain = originalTestChainEnabled,
+ enablePkgs = originalUpdaterPkgsStatus
+ )
clearGmsUpdateOverride(testInfo)
}
}
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 6e9d614..e95a81a 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -201,3 +201,8 @@
name: "connectivity-mainline-presubmit-java-defaults",
test_mainline_modules: mainline_presubmit_modules,
}
+
+filegroup {
+ name: "connectivity_mainline_test_map",
+ srcs: ["connectivity_mainline_test.map"],
+}
diff --git a/tests/common/connectivity_mainline_test.map b/tests/common/connectivity_mainline_test.map
new file mode 100644
index 0000000..043312e
--- /dev/null
+++ b/tests/common/connectivity_mainline_test.map
@@ -0,0 +1,27 @@
+#
+# 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.
+#
+
+# Some connectivity tests run on older OS versions, and for those tests, many
+# library dependencies (such as libbase and libc++) need to be linked
+# statically. The tests also need to be linked with a version script to ensure
+# that the statically-linked library isn't exported from the executable, where
+# it would override the shared libraries that the OS itself uses. See
+# b/333438055 for an example of what goes wrong when libc++ is partially
+# exported from an executable.
+{
+ local:
+ *;
+};
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 6eb56c7b..0f0e2f1 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -61,6 +61,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
import static android.os.Process.INVALID_UID;
+
import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.modules.utils.build.SdkLevel.isAtLeastV;
@@ -68,6 +69,7 @@
import static com.android.testutils.MiscAsserts.assertEmpty;
import static com.android.testutils.MiscAsserts.assertThrows;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -83,21 +85,25 @@
import android.os.Build;
import android.util.ArraySet;
import android.util.Range;
+
import androidx.test.filters.SmallTest;
+
import com.android.testutils.CompatUtil;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
// NetworkCapabilities is only updatable on S+, and this test covers behavior which implementation
@@ -1465,4 +1471,26 @@
assertEquals("-SUPL-VALIDATED-CAPTIVE_PORTAL+MMS+OEM_PAID",
nc1.describeCapsDifferencesFrom(nc2));
}
+
+ @Test
+ public void testInvalidCapability() {
+ final int invalidCapability = Integer.MAX_VALUE;
+ // Passing invalid capability does not throw
+ final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addForbiddenCapability(NET_CAPABILITY_NOT_ROAMING)
+ .removeCapability(invalidCapability)
+ .removeForbiddenCapability(invalidCapability)
+ .addCapability(invalidCapability)
+ .addForbiddenCapability(invalidCapability)
+ .build();
+
+ final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addForbiddenCapability(NET_CAPABILITY_NOT_ROAMING)
+ .build();
+
+ // nc1 and nc2 are the same since invalid capability is ignored
+ assertEquals(nc1, nc2);
+ }
}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
index da633c0..00f67f4 100644
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
@@ -16,7 +16,7 @@
package com.android.cts.netpolicy.hostside;
-import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
import static org.junit.Assume.assumeTrue;
@@ -46,14 +46,15 @@
public final void tearDown() throws Exception {
super.tearDown();
+ stopApp();
removePowerSaveModeWhitelist(TEST_APP2_PKG);
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
}
@Test
public void testFgsNetworkAccess() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkAccess(false, null);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
@@ -61,8 +62,8 @@
@Test
public void testActivityNetworkAccess() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkAccess(false, null);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
@@ -70,23 +71,23 @@
@Test
public void testBackgroundNetworkAccess_inFullAllowlist() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkAccess(false, null);
addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
assertNetworkAccess(true, null);
}
@Test
public void testBackgroundNetworkAccess_inExceptIdleAllowlist() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkAccess(false, null);
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
assertNetworkAccess(true, null);
}
}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index d0203c5..0f5f58c 100644
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -96,7 +96,12 @@
protected static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
protected static final String TEST_APP2_PKG = "com.android.cts.netpolicy.hostside.app2";
// TODO(b/321797685): Configure it via device-config once it is available.
- protected static final long PROCESS_STATE_TRANSITION_DELAY_MS = TimeUnit.SECONDS.toMillis(5);
+ protected final long mProcessStateTransitionLongDelayMs =
+ useDifferentDelaysForBackgroundChain() ? TimeUnit.SECONDS.toMillis(20)
+ : TimeUnit.SECONDS.toMillis(5);
+ protected final long mProcessStateTransitionShortDelayMs =
+ useDifferentDelaysForBackgroundChain() ? TimeUnit.SECONDS.toMillis(2)
+ : TimeUnit.SECONDS.toMillis(5);
private static final String TEST_APP2_ACTIVITY_CLASS = TEST_APP2_PKG + ".MyActivity";
private static final String TEST_APP2_SERVICE_CLASS = TEST_APP2_PKG + ".MyForegroundService";
@@ -241,6 +246,22 @@
return Boolean.parseBoolean(output);
}
+ /**
+ * Check if the flag to use different delays for sensitive proc-states is enabled.
+ * This is a manual check because the feature flag infrastructure may not be available
+ * in all the branches that will get this code.
+ * TODO: b/322115994 - Use @RequiresFlagsEnabled with
+ * Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN once the tests are moved to cts.
+ */
+ private boolean useDifferentDelaysForBackgroundChain() {
+ if (!SdkLevel.isAtLeastV()) {
+ return false;
+ }
+ final String output = executeShellCommand("device_config get backstage_power"
+ + " com.android.server.net.use_different_delays_for_background_chain");
+ return Boolean.parseBoolean(output);
+ }
+
protected int getUid(String packageName) throws Exception {
return mContext.getPackageManager().getPackageUid(packageName, 0);
}
@@ -824,6 +845,10 @@
assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
}
+ protected void stopApp() {
+ executeSilentShellCommand("am stop-app " + TEST_APP2_PKG);
+ }
+
protected void setAppIdle(boolean isIdle) throws Exception {
setAppIdleNoAssert(isIdle);
assertAppIdle(isIdle);
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
index 811190f..bfccce9 100644
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
@@ -53,7 +53,7 @@
@After
public final void tearDown() throws Exception {
super.tearDown();
- finishActivity();
+ stopApp();
resetDeviceState();
}
@@ -108,7 +108,7 @@
assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
assertLaunchedActivityHasNetworkAccess("testStartActivity_default", () -> {
assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ SystemClock.sleep(mProcessStateTransitionLongDelayMs);
assertNetworkAccess(false, null);
});
}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
index 7038d02..3934cfa 100644
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
@@ -268,6 +268,7 @@
setRestrictBackground(false);
setBatterySaverMode(false);
unregisterNetworkCallback();
+ stopApp();
if (SdkLevel.isAtLeastT() && (mCtsNetUtils != null)) {
mCtsNetUtils.restorePrivateDnsSetting();
@@ -387,7 +388,7 @@
finishActivity();
assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ SystemClock.sleep(mProcessStateTransitionLongDelayMs);
assertNetworkAccess(false, null);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
@@ -413,7 +414,7 @@
finishActivity();
assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ SystemClock.sleep(mProcessStateTransitionLongDelayMs);
assertNetworkAccess(false, null);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
index 9b3fe9f..6c5f2ff 100644
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
@@ -17,6 +17,7 @@
package com.android.cts.netpolicy.hostside;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static android.os.Process.SYSTEM_UID;
@@ -65,6 +66,7 @@
setRestrictBackground(false);
setRestrictedNetworkingMode(false);
unregisterNetworkCallback();
+ stopApp();
}
@Test
@@ -248,8 +250,8 @@
assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
try {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
+ SystemClock.sleep(mProcessStateTransitionShortDelayMs);
assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
@@ -260,7 +262,7 @@
finishActivity();
assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ SystemClock.sleep(mProcessStateTransitionLongDelayMs);
assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index 23e30a0..14d5d54 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-next_app_data = []
+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
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 50d6e76..e186c6b 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
@@ -22,6 +22,7 @@
import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
import static android.content.pm.PackageManager.FEATURE_WIFI;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
@@ -51,6 +52,7 @@
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.RecorderCallback.CallbackEntry.BLOCKED_STATUS_INT;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -969,19 +971,30 @@
final TestableNetworkCallback otherUidCallback = new TestableNetworkCallback();
final TestableNetworkCallback myUidCallback = new TestableNetworkCallback();
if (SdkLevel.isAtLeastS()) {
- final int otherUid =
- UserHandle.of(5 /* userId */).getUid(Process.FIRST_APPLICATION_UID);
+ // Using the same appId with the test to make sure otherUid has the internet permission.
+ // This works because the UID permission map only stores the app ID and not the whole
+ // UID. If the otherUid does not have the internet permission, network access from
+ // otherUid could be considered blocked on V+.
+ final int appId = UserHandle.getAppId(Process.myUid());
+ final int otherUid = UserHandle.of(5 /* userId */).getUid(appId);
final Handler h = new Handler(Looper.getMainLooper());
runWithShellPermissionIdentity(() -> {
registerSystemDefaultNetworkCallback(systemDefaultCallback, h);
registerDefaultNetworkCallbackForUid(otherUid, otherUidCallback, h);
registerDefaultNetworkCallbackForUid(Process.myUid(), myUidCallback, h);
}, NETWORK_SETTINGS);
- for (TestableNetworkCallback callback :
- List.of(systemDefaultCallback, otherUidCallback, myUidCallback)) {
+ for (TestableNetworkCallback callback : List.of(systemDefaultCallback, myUidCallback)) {
callback.expectAvailableCallbacks(defaultNetwork, false /* suspended */,
true /* validated */, false /* blocked */, TIMEOUT_MS);
}
+ // On V+, ConnectivityService generates blockedReasons based on bpf map contents even if
+ // the otherUid does not exist on device. So if the background chain is enabled,
+ // otherUid is blocked.
+ final boolean isOtherUidBlocked = SdkLevel.isAtLeastV()
+ && runAsShell(NETWORK_SETTINGS, () -> mCM.getFirewallChainEnabled(
+ FIREWALL_CHAIN_BACKGROUND));
+ otherUidCallback.expectAvailableCallbacks(defaultNetwork, false /* suspended */,
+ true /* validated */, isOtherUidBlocked, TIMEOUT_MS);
}
FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index ad25562..12ea23b 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -35,4 +35,5 @@
"general-tests",
"sts",
],
+ sdk_version: "test_current",
}
diff --git a/tests/cts/hostside/networkslicingtestapp/Android.bp b/tests/cts/hostside/networkslicingtestapp/Android.bp
index 100b6e4..c220000 100644
--- a/tests/cts/hostside/networkslicingtestapp/Android.bp
+++ b/tests/cts/hostside/networkslicingtestapp/Android.bp
@@ -44,6 +44,7 @@
"CtsHostsideNetworkCapTestsAppDefaults",
],
manifest: "AndroidManifestWithoutProperty.xml",
+ sdk_version: "test_current",
}
android_test_helper_app {
@@ -53,6 +54,7 @@
"CtsHostsideNetworkCapTestsAppDefaults",
],
manifest: "AndroidManifestWithProperty.xml",
+ sdk_version: "test_current",
}
android_test_helper_app {
@@ -63,4 +65,5 @@
],
target_sdk_version: "33",
manifest: "AndroidManifestWithoutProperty.xml",
+ sdk_version: "test_current",
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index f0a87af..cea60f9 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -18,8 +18,6 @@
import android.platform.test.annotations.RequiresDevice;
-import com.android.testutils.SkipPresubmit;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -37,13 +35,11 @@
uninstallPackage(TEST_APP2_PKG, true);
}
- @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testChangeUnderlyingNetworks() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testChangeUnderlyingNetworks");
}
- @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testDefault() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testDefault");
@@ -166,7 +162,6 @@
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBlockIncomingPackets");
}
- @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testSetVpnDefaultForUids() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetVpnDefaultForUids");
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
index 5ac4229..1d30d68 100644
--- a/tests/cts/multidevices/Android.bp
+++ b/tests/cts/multidevices/Android.bp
@@ -14,12 +14,16 @@
package {
default_applicable_licenses: ["Android-Apache-2.0"],
+ default_team: "trendy_team_fwk_core_networking",
}
python_test_host {
name: "CtsConnectivityMultiDevicesTestCases",
main: "connectivity_multi_devices_test.py",
- srcs: ["connectivity_multi_devices_test.py"],
+ srcs: [
+ "connectivity_multi_devices_test.py",
+ "utils/*.py",
+ ],
libs: [
"mobly",
],
diff --git a/tests/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
index ab88504..abd6fe2 100644
--- a/tests/cts/multidevices/connectivity_multi_devices_test.py
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -1,23 +1,17 @@
# Lint as: python3
"""Connectivity multi devices tests."""
-import base64
import sys
-import uuid
-
-from mobly import asserts
from mobly import base_test
from mobly import test_runner
from mobly import utils
from mobly.controllers import android_device
+from utils import mdns_utils
+from utils import tether_utils
+from utils.tether_utils import UpstreamType
CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE = "com.google.snippet.connectivity"
-class UpstreamType:
- CELLULAR = 1
- WIFI = 2
-
-
class ConnectivityMultiDevicesTest(base_test.BaseTestClass):
def setup_class(self):
@@ -40,67 +34,53 @@
raise_on_exception=True,
)
- @staticmethod
- def generate_uuid32_base64():
- """Generates a UUID32 and encodes it in Base64.
-
- Returns:
- str: The Base64-encoded UUID32 string. Which is 22 characters.
- """
- return base64.b64encode(uuid.uuid1().bytes).decode("utf-8").strip("=")
-
- def _do_test_hotspot_for_upstream_type(self, upstream_type):
- """Test hotspot with the specified upstream type.
-
- This test create a hotspot, make the client connect
- to it, and verify the packet is forwarded by the hotspot.
- """
- server = self.serverDevice.connectivity_multi_devices_snippet
- client = self.clientDevice.connectivity_multi_devices_snippet
-
- # Assert pre-conditions specific to each upstream type.
- asserts.skip_if(not client.hasWifiFeature(), "Client requires Wifi feature")
- asserts.skip_if(
- not server.hasHotspotFeature(), "Server requires hotspot feature"
- )
- if upstream_type == UpstreamType.CELLULAR:
- asserts.skip_if(
- not server.hasTelephonyFeature(), "Server requires Telephony feature"
- )
- server.requestCellularAndEnsureDefault()
- elif upstream_type == UpstreamType.WIFI:
- asserts.skip_if(
- not server.isStaApConcurrencySupported(),
- "Server requires Wifi AP + STA concurrency",
- )
- server.ensureWifiIsDefault()
- else:
- raise ValueError(f"Invalid upstream type: {upstream_type}")
-
- # Generate ssid/passphrase with random characters to make sure nearby devices won't
- # connect unexpectedly. Note that total length of ssid cannot go over 32.
- testSsid = "HOTSPOT-" + self.generate_uuid32_base64()
- testPassphrase = self.generate_uuid32_base64()
-
- try:
- # Create a hotspot with fixed SSID and password.
- server.startHotspot(testSsid, testPassphrase)
-
- # Make the client connects to the hotspot.
- client.connectToWifi(testSsid, testPassphrase, True)
-
- finally:
- if upstream_type == UpstreamType.CELLULAR:
- server.unrequestCellular()
- # Teardown the hotspot.
- server.stopAllTethering()
-
def test_hotspot_upstream_wifi(self):
- self._do_test_hotspot_for_upstream_type(UpstreamType.WIFI)
+ tether_utils.assume_hotspot_test_preconditions(
+ self.serverDevice, self.clientDevice, UpstreamType.WIFI
+ )
+ try:
+ # Connectivity of the client verified by asserting the validated capability.
+ tether_utils.setup_hotspot_and_client_for_upstream_type(
+ self.serverDevice, self.clientDevice, UpstreamType.WIFI
+ )
+ finally:
+ tether_utils.cleanup_tethering_for_upstream_type(
+ self.serverDevice, UpstreamType.WIFI
+ )
def test_hotspot_upstream_cellular(self):
- self._do_test_hotspot_for_upstream_type(UpstreamType.CELLULAR)
+ tether_utils.assume_hotspot_test_preconditions(
+ self.serverDevice, self.clientDevice, UpstreamType.CELLULAR
+ )
+ try:
+ # Connectivity of the client verified by asserting the validated capability.
+ tether_utils.setup_hotspot_and_client_for_upstream_type(
+ self.serverDevice, self.clientDevice, UpstreamType.CELLULAR
+ )
+ finally:
+ tether_utils.cleanup_tethering_for_upstream_type(
+ self.serverDevice, UpstreamType.CELLULAR
+ )
+ def test_mdns_via_hotspot(self):
+ tether_utils.assume_hotspot_test_preconditions(
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
+ )
+ try:
+ # Connectivity of the client verified by asserting the validated capability.
+ tether_utils.setup_hotspot_and_client_for_upstream_type(
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
+ )
+ mdns_utils.register_mdns_service_and_discover_resolve(
+ self.clientDevice, self.serverDevice
+ )
+ finally:
+ mdns_utils.cleanup_mdns_service(
+ self.clientDevice, self.serverDevice
+ )
+ tether_utils.cleanup_tethering_for_upstream_type(
+ self.serverDevice, UpstreamType.NONE
+ )
if __name__ == "__main__":
# Take test args
diff --git a/tests/cts/multidevices/snippet/Android.bp b/tests/cts/multidevices/snippet/Android.bp
index 5940cbb..b0b32c2 100644
--- a/tests/cts/multidevices/snippet/Android.bp
+++ b/tests/cts/multidevices/snippet/Android.bp
@@ -25,6 +25,7 @@
],
srcs: [
"ConnectivityMultiDevicesSnippet.kt",
+ "MdnsMultiDevicesSnippet.kt",
],
manifest: "AndroidManifest.xml",
static_libs: [
diff --git a/tests/cts/multidevices/snippet/AndroidManifest.xml b/tests/cts/multidevices/snippet/AndroidManifest.xml
index 9ed8146..967e581 100644
--- a/tests/cts/multidevices/snippet/AndroidManifest.xml
+++ b/tests/cts/multidevices/snippet/AndroidManifest.xml
@@ -27,7 +27,8 @@
of a snippet class -->
<meta-data
android:name="mobly-snippets"
- android:value="com.google.snippet.connectivity.ConnectivityMultiDevicesSnippet" />
+ android:value="com.google.snippet.connectivity.ConnectivityMultiDevicesSnippet,
+ com.google.snippet.connectivity.MdnsMultiDevicesSnippet" />
</application>
<!-- Add an instrumentation tag so that the app can be launched through an
instrument command. The runner `com.google.android.mobly.snippet.SnippetRunner`
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
index 258648f..f4ad2c4 100644
--- a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -16,11 +16,11 @@
package com.google.snippet.connectivity
+import android.Manifest.permission.NETWORK_SETTINGS
import android.Manifest.permission.OVERRIDE_WIFI_CONFIG
import android.content.pm.PackageManager.FEATURE_TELEPHONY
import android.content.pm.PackageManager.FEATURE_WIFI
import android.net.ConnectivityManager
-import android.net.Network
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
@@ -30,20 +30,24 @@
import android.net.wifi.SoftApConfiguration
import android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
import android.net.wifi.WifiConfiguration
+import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiNetworkSpecifier
import android.net.wifi.WifiSsid
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.AutoReleaseNetworkCallbackRule
import com.android.testutils.ConnectUtil
import com.android.testutils.NetworkCallbackHelper
-import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.runAsShell
import com.google.android.mobly.snippet.Snippet
import com.google.android.mobly.snippet.rpc.Rpc
+import org.junit.Rule
class ConnectivityMultiDevicesSnippet : Snippet {
+ @get:Rule
+ val networkCallbackRule = AutoReleaseNetworkCallbackRule()
private val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
private val wifiManager = context.getSystemService(WifiManager::class.java)!!
private val cm = context.getSystemService(ConnectivityManager::class.java)!!
@@ -73,9 +77,9 @@
ctsNetUtils.expectNetworkIsSystemDefault(network)
}
- @Rpc(description = "Unrequest cellular connection.")
- fun unrequestCellular() {
- cbHelper.unrequestCell()
+ @Rpc(description = "Unregister all connections.")
+ fun unregisterAll() {
+ cbHelper.unregisterAll()
}
@Rpc(description = "Ensure any wifi is connected and is the default network.")
@@ -88,10 +92,8 @@
// Suppress warning because WifiManager methods to connect to a config are
// documented not to be deprecated for privileged users.
@Suppress("DEPRECATION")
- fun connectToWifi(ssid: String, passphrase: String, requireValidation: Boolean): Network {
+ fun connectToWifi(ssid: String, passphrase: String): Long {
val specifier = WifiNetworkSpecifier.Builder()
- .setSsid(ssid)
- .setWpa2Passphrase(passphrase)
.setBand(ScanResult.WIFI_BAND_24_GHZ)
.build()
val wifiConfig = WifiConfiguration()
@@ -102,27 +104,28 @@
wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
- // Register network callback for the specific wifi.
+ // Add the test configuration and connect to it.
+ val connectUtil = ConnectUtil(context)
+ connectUtil.connectToWifiConfig(wifiConfig)
+
+ // Implement manual SSID matching. Specifying the SSID in
+ // NetworkSpecifier is ineffective
+ // (see WifiNetworkAgentSpecifier#canBeSatisfiedBy for details).
+ // Note that holding permission is necessary when waiting for
+ // the callbacks. The handler thread checks permission; if
+ // it's not present, the SSID will be redacted.
val networkCallback = TestableNetworkCallback()
- val wifiRequest = NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI)
- .setNetworkSpecifier(specifier)
- .build()
- cm.registerNetworkCallback(wifiRequest, networkCallback)
-
- try {
- // Add the test configuration and connect to it.
- val connectUtil = ConnectUtil(context)
- connectUtil.connectToWifiConfig(wifiConfig)
-
- val event = networkCallback.expect<Available>()
- if (requireValidation) {
- networkCallback.eventuallyExpect<CapabilitiesChanged> {
- it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
- }
- }
- return event.network
- } finally {
- cm.unregisterNetworkCallback(networkCallback)
+ val wifiRequest = NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build()
+ return runAsShell(NETWORK_SETTINGS) {
+ // Register the network callback is needed here.
+ // This is to avoid the race condition where callback is fired before
+ // acquiring permission.
+ networkCallbackRule.registerNetworkCallback(wifiRequest, networkCallback)
+ return@runAsShell networkCallback.eventuallyExpect<CapabilitiesChanged> {
+ // Remove double quotes.
+ val ssidFromCaps = (WifiInfo::sanitizeSsid)(it.caps.ssid)
+ ssidFromCaps == ssid && it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
+ }.network.networkHandle
}
}
diff --git a/tests/cts/multidevices/snippet/MdnsMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/MdnsMultiDevicesSnippet.kt
new file mode 100644
index 0000000..1b288df
--- /dev/null
+++ b/tests/cts/multidevices/snippet/MdnsMultiDevicesSnippet.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.google.snippet.connectivity
+
+import android.net.nsd.NsdManager
+import android.net.nsd.NsdServiceInfo
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.NsdDiscoveryRecord
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
+import com.android.testutils.NsdRegistrationRecord
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
+import com.android.testutils.NsdResolveRecord
+import com.android.testutils.NsdResolveRecord.ResolveEvent.ServiceResolved
+import com.google.android.mobly.snippet.Snippet
+import com.google.android.mobly.snippet.rpc.Rpc
+import kotlin.test.assertEquals
+import org.junit.Assert.assertArrayEquals
+
+private const val SERVICE_NAME = "MultiDevicesTest"
+private const val SERVICE_TYPE = "_multi_devices._tcp"
+private const val SERVICE_ATTRIBUTES_KEY = "key"
+private const val SERVICE_ATTRIBUTES_VALUE = "value"
+private const val SERVICE_PORT = 12345
+private const val REGISTRATION_TIMEOUT_MS = 10_000L
+
+class MdnsMultiDevicesSnippet : Snippet {
+ private val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ private val nsdManager = context.getSystemService(NsdManager::class.java)!!
+ private val registrationRecord = NsdRegistrationRecord()
+ private val discoveryRecord = NsdDiscoveryRecord()
+ private val resolveRecord = NsdResolveRecord()
+
+ @Rpc(description = "Register a mDns service")
+ fun registerMDnsService() {
+ val info = NsdServiceInfo()
+ info.setServiceName(SERVICE_NAME)
+ info.setServiceType(SERVICE_TYPE)
+ info.setPort(SERVICE_PORT)
+ info.setAttribute(SERVICE_ATTRIBUTES_KEY, SERVICE_ATTRIBUTES_VALUE)
+ nsdManager.registerService(info, NsdManager.PROTOCOL_DNS_SD, registrationRecord)
+ registrationRecord.expectCallback<ServiceRegistered>(REGISTRATION_TIMEOUT_MS)
+ }
+
+ @Rpc(description = "Unregister a mDns service")
+ fun unregisterMDnsService() {
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+
+ @Rpc(description = "Ensure the discovery and resolution of the mDNS service")
+ // Suppress the warning, as the NsdManager#resolveService() method is deprecated.
+ @Suppress("DEPRECATION")
+ fun ensureMDnsServiceDiscoveryAndResolution() {
+ // Discover a mDns service that matches the test service
+ nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
+ val info = discoveryRecord.waitForServiceDiscovered(SERVICE_NAME, SERVICE_TYPE)
+ // Resolve the retrieved mDns service.
+ nsdManager.resolveService(info, resolveRecord)
+ val serviceResolved = resolveRecord.expectCallbackEventually<ServiceResolved>()
+ serviceResolved.serviceInfo.let {
+ assertEquals(SERVICE_NAME, it.serviceName)
+ assertEquals(".$SERVICE_TYPE", it.serviceType)
+ assertEquals(SERVICE_PORT, it.port)
+ assertEquals(1, it.attributes.size)
+ assertArrayEquals(
+ SERVICE_ATTRIBUTES_VALUE.encodeToByteArray(),
+ it.attributes[SERVICE_ATTRIBUTES_KEY]
+ )
+ }
+ }
+
+ @Rpc(description = "Stop discovery")
+ fun stopMDnsServiceDiscovery() {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+ discoveryRecord.expectCallbackEventually<DiscoveryStopped>()
+ }
+}
diff --git a/tests/cts/multidevices/utils/mdns_utils.py b/tests/cts/multidevices/utils/mdns_utils.py
new file mode 100644
index 0000000..ec1fea0
--- /dev/null
+++ b/tests/cts/multidevices/utils/mdns_utils.py
@@ -0,0 +1,42 @@
+# 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.controllers import android_device
+
+
+def register_mdns_service_and_discover_resolve(
+ advertising_device: android_device, discovery_device: android_device
+) -> None:
+ """Test mdns advertising, discovery and resolution
+
+ One device registers an mDNS service, and another device discovers and
+ resolves that service.
+ """
+ advertising = advertising_device.connectivity_multi_devices_snippet
+ discovery = discovery_device.connectivity_multi_devices_snippet
+
+ # Register a mDns service
+ advertising.registerMDnsService()
+
+ # Ensure the discovery and resolution of the mDNS service
+ discovery.ensureMDnsServiceDiscoveryAndResolution()
+
+
+def cleanup_mdns_service(
+ advertising_device: android_device, discovery_device: android_device
+) -> None:
+ # Unregister the mDns service
+ advertising_device.connectivity_multi_devices_snippet.unregisterMDnsService()
+ # Stop discovery
+ discovery_device.connectivity_multi_devices_snippet.stopMDnsServiceDiscovery()
diff --git a/tests/cts/multidevices/utils/tether_utils.py b/tests/cts/multidevices/utils/tether_utils.py
new file mode 100644
index 0000000..702b596
--- /dev/null
+++ b/tests/cts/multidevices/utils/tether_utils.py
@@ -0,0 +1,110 @@
+# 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.
+
+import base64
+import uuid
+
+from mobly import asserts
+from mobly.controllers import android_device
+
+
+class UpstreamType:
+ NONE = 0
+ CELLULAR = 1
+ WIFI = 2
+
+
+def generate_uuid32_base64() -> str:
+ """Generates a UUID32 and encodes it in Base64.
+
+ Returns:
+ str: The Base64-encoded UUID32 string. Which is 22 characters.
+ """
+ # Strip padding characters to make it safer for hotspot name length limit.
+ return base64.b64encode(uuid.uuid1().bytes).decode("utf-8").strip("=")
+
+
+def assume_hotspot_test_preconditions(
+ server_device: android_device,
+ client_device: android_device,
+ upstream_type: UpstreamType,
+) -> None:
+ server = server_device.connectivity_multi_devices_snippet
+ client = client_device.connectivity_multi_devices_snippet
+
+ # Assert pre-conditions specific to each upstream type.
+ asserts.skip_if(not client.hasWifiFeature(), "Client requires Wifi feature")
+ asserts.skip_if(
+ not server.hasHotspotFeature(), "Server requires hotspot feature"
+ )
+ if upstream_type == UpstreamType.CELLULAR:
+ asserts.skip_if(
+ not server.hasTelephonyFeature(), "Server requires Telephony feature"
+ )
+ elif upstream_type == UpstreamType.WIFI:
+ asserts.skip_if(
+ not server.isStaApConcurrencySupported(),
+ "Server requires Wifi AP + STA concurrency",
+ )
+ elif upstream_type == UpstreamType.NONE:
+ pass
+ else:
+ raise ValueError(f"Invalid upstream type: {upstream_type}")
+
+
+def setup_hotspot_and_client_for_upstream_type(
+ server_device: android_device,
+ client_device: android_device,
+ upstream_type: UpstreamType,
+) -> (str, int):
+ """Setup the hotspot with a connected client with the specified upstream type.
+
+ This creates a hotspot, make the client connect
+ to it, and verify the packet is forwarded by the hotspot.
+ And returns interface name of both if successful.
+ """
+ server = server_device.connectivity_multi_devices_snippet
+ client = client_device.connectivity_multi_devices_snippet
+
+ if upstream_type == UpstreamType.CELLULAR:
+ server.requestCellularAndEnsureDefault()
+ elif upstream_type == UpstreamType.WIFI:
+ server.ensureWifiIsDefault()
+ elif upstream_type == UpstreamType.NONE:
+ pass
+ else:
+ raise ValueError(f"Invalid upstream type: {upstream_type}")
+
+ # Generate ssid/passphrase with random characters to make sure nearby devices won't
+ # connect unexpectedly. Note that total length of ssid cannot go over 32.
+ test_ssid = "HOTSPOT-" + generate_uuid32_base64()
+ test_passphrase = generate_uuid32_base64()
+
+ # Create a hotspot with fixed SSID and password.
+ hotspot_interface = server.startHotspot(test_ssid, test_passphrase)
+
+ # Make the client connects to the hotspot.
+ client_network = client.connectToWifi(test_ssid, test_passphrase)
+
+ return hotspot_interface, client_network
+
+
+def cleanup_tethering_for_upstream_type(
+ server_device: android_device, upstream_type: UpstreamType
+) -> None:
+ server = server_device.connectivity_multi_devices_snippet
+ if upstream_type == UpstreamType.CELLULAR:
+ server.unregisterAll()
+ # Teardown the hotspot.
+ server.stopAllTethering()
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 768ba12..ae85701 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -180,4 +180,5 @@
"cts",
"general-tests",
],
+ sdk_version: "test_current",
}
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 38f26d8..077c3ef 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -24,6 +24,11 @@
<option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk" />
<option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.tethering.apex" />
<option name="not-shardable" value="true" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+ <option name="target" value="device" />
+ <option name="config-filename" value="{MODULE}" />
+ <option name="version" value="1.0" />
+ </target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="{MODULE}.apk" />
@@ -38,6 +43,7 @@
<option name="runtime-hint" value="9m4s" />
<option name="hidden-api-checks" value="false" />
<option name="isolated-storage" value="false" />
+ <option name="instrumentation-arg" key="test-module-name" value="{MODULE}" />
<!-- Test filter that allows test APKs to select which tests they want to run by annotating
those tests with an annotation matching the name of the APK.
diff --git a/tests/cts/net/DynamicConfig.xml b/tests/cts/net/DynamicConfig.xml
new file mode 100644
index 0000000..af019c2
--- /dev/null
+++ b/tests/cts/net/DynamicConfig.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+
+<dynamicConfig>
+ <entry key="remote_config_required">
+ <value>false</value>
+ </entry>
+ <entry key="IP_ADDRESS_ECHO_URL">
+ <value>https://google-ipv6test.appspot.com/ip.js?fmt=text</value>
+ </entry>
+</dynamicConfig>
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
index 2ec3a70..587d5a5 100644
--- a/tests/cts/net/api23Test/Android.bp
+++ b/tests/cts/net/api23Test/Android.bp
@@ -56,4 +56,5 @@
":CtsNetTestAppForApi23",
],
per_testcase_directory: true,
+ sdk_version: "test_current",
}
diff --git a/tests/cts/net/native/dns/Android.bp b/tests/cts/net/native/dns/Android.bp
index 8e24fba..de4a3bf 100644
--- a/tests/cts/net/native/dns/Android.bp
+++ b/tests/cts/net/native/dns/Android.bp
@@ -3,6 +3,11 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+filegroup {
+ name: "dns_async_test_default_map",
+ srcs: ["dns_async_test_default.map"],
+}
+
cc_defaults {
name: "dns_async_defaults",
@@ -20,6 +25,14 @@
srcs: [
"NativeDnsAsyncTest.cpp",
],
+ // This test runs on older platform versions, so many libraries (such as libbase and libc++)
+ // need to be linked statically. The test also needs to be linked with a version script to
+ // ensure that the statically-linked library isn't exported from the executable, where it
+ // would override the shared libraries that the platform itself uses.
+ // See http://b/333438055 for an example of what goes wrong when libc++ is partially exported
+ // from an executable.
+ version_script: ":dns_async_test_default_map",
+ stl: "libc++_static",
shared_libs: [
"libandroid",
"liblog",
diff --git a/tests/cts/net/native/dns/dns_async_test_default.map b/tests/cts/net/native/dns/dns_async_test_default.map
new file mode 100644
index 0000000..e342e43
--- /dev/null
+++ b/tests/cts/net/native/dns/dns_async_test_default.map
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+
+{
+ local:
+ *;
+};
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index a679498..f6cbeeb 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -29,7 +29,11 @@
import android.net.apf.ApfConstants.ETH_ETHERTYPE_OFFSET
import android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET
import android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET
+import android.net.apf.ApfCounterTracker
+import android.net.apf.ApfCounterTracker.Counter.FILTER_AGE_16384THS
import android.net.apf.ApfV4Generator
+import android.net.apf.ApfV4GeneratorBase
+import android.net.apf.ApfV6Generator
import android.net.apf.BaseApfGenerator
import android.net.apf.BaseApfGenerator.MemorySlot
import android.net.apf.BaseApfGenerator.Register.R0
@@ -51,9 +55,11 @@
import android.util.Log
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel
import com.android.compatibility.common.util.PropertyUtil.getVsrApiLevel
import com.android.compatibility.common.util.SystemUtil.runShellCommand
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.android.compatibility.common.util.VsrTest
import com.android.internal.util.HexDump
import com.android.net.module.util.PacketReader
import com.android.testutils.DevSdkIgnoreRule
@@ -71,7 +77,6 @@
import com.google.common.truth.Truth.assertWithMessage
import com.google.common.truth.TruthJUnit.assume
import java.io.FileDescriptor
-import java.lang.Thread
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.util.concurrent.CompletableFuture
@@ -191,8 +196,8 @@
futureReply!!.complete(recvbuf.sliceArray(8..<length))
}
- fun sendPing(data: ByteArray) {
- require(data.size == 56)
+ fun sendPing(data: ByteArray, payloadSize: Int) {
+ require(data.size == payloadSize)
// rfc4443#section-4.1: Echo Request Message
// 0 1 2 3
@@ -299,6 +304,10 @@
}
}
+ @VsrTest(
+ requirements = ["VSR-5.3.12-001", "VSR-5.3.12-003", "VSR-5.3.12-004", "VSR-5.3.12-009",
+ "VSR-5.3.12-012"]
+ )
@Test
fun testApfCapabilities() {
// APF became mandatory in Android 14 VSR.
@@ -318,7 +327,7 @@
if (caps.apfVersionSupported > 4) {
assertThat(caps.maximumApfProgramSize).isAtLeast(2048)
- assertThat(caps.apfVersionSupported).isEqualTo(6000) // v6.0000
+ assertThat(caps.apfVersionSupported).isEqualTo(6000) // v6.0000
}
// DEVICEs launching with Android 15 (AOSP experimental) or higher with CHIPSETs that set
@@ -349,16 +358,26 @@
return HexDump.hexStringToByteArray(progHexString)
}
+ @VsrTest(
+ requirements = ["VSR-5.3.12-007", "VSR-5.3.12-008", "VSR-5.3.12-010", "VSR-5.3.12-011"]
+ )
@SkipPresubmit(reason = "This test takes longer than 1 minute, do not run it on presubmit.")
// APF integration is mostly broken before V, only run the full read / write test on V+.
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @Test
+ // Increase timeout for test to 15 minutes to accommodate device with large APF RAM.
+ @Test(timeout = 15 * 60 * 1000)
fun testReadWriteProgram() {
assumeApfVersionSupportAtLeast(4)
- // Only test down to 2 bytes. The first byte always stays PASS.
+ val minReadWriteSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ 2
+ } else {
+ 8
+ }
+
+ // The minReadWriteSize is 2 bytes. The first byte always stays PASS.
val program = ByteArray(caps.maximumApfProgramSize)
- for (i in caps.maximumApfProgramSize downTo 2) {
+ for (i in caps.maximumApfProgramSize downTo minReadWriteSize) {
// Randomize bytes in range [1, i). And install first [0, i) bytes of program.
// Note that only the very first instruction (PASS) is valid APF bytecode.
Random.nextBytes(program, 1 /* fromIndex */, i /* toIndex */)
@@ -366,11 +385,19 @@
// Compare entire memory region.
val readResult = readProgram()
- assertWithMessage("read/write $i byte prog failed").that(readResult).isEqualTo(program)
+ val errMsg = """
+ read/write $i byte prog failed.
+ In APFv4, the APF memory region MUST NOT be modified or cleared except by APF
+ instructions executed by the interpreter or by Android OS calls to the HAL. If this
+ requirement cannot be met, the firmware cannot declare that it supports APFv4 and
+ it should declare that it only supports APFv3(if counter is partially supported) or
+ APFv2.
+ """.trimIndent()
+ assertWithMessage(errMsg).that(readResult).isEqualTo(program)
}
}
- fun ApfV4Generator.addPassIfNotIcmpv6EchoReply() {
+ fun ApfV4GeneratorBase<*>.addPassIfNotIcmpv6EchoReply() {
// If not IPv6 -> PASS
addLoad16(R0, ETH_ETHERTYPE_OFFSET)
addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), BaseApfGenerator.PASS_LABEL)
@@ -385,23 +412,41 @@
}
// APF integration is mostly broken before V
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
fun testDropPingReply() {
+ // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
+ // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
+ // should be turned on.
+ assume().that(getVsrApiLevel()).isAtLeast(34)
assumeApfVersionSupportAtLeast(4)
// clear any active APF filter
- var gen = ApfV4Generator(4).addPass()
+ var gen = ApfV4Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ ).addPass()
installProgram(gen.generate())
readProgram() // wait for install completion
// Assert that initial ping does not get filtered.
- val data = ByteArray(56).also { Random.nextBytes(it) }
- packetReader.sendPing(data)
+ val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ 68
+ } else {
+ 4
+ }
+ val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ packetReader.sendPing(data, payloadSize)
assertThat(packetReader.expectPingReply()).isEqualTo(data)
// Generate an APF program that drops the next ping
- gen = ApfV4Generator(4)
+ gen = ApfV4Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
// If not ICMPv6 Echo Reply -> PASS
gen.addPassIfNotIcmpv6EchoReply()
@@ -417,20 +462,29 @@
installProgram(program)
readProgram() // wait for install completion
- packetReader.sendPing(data)
+ packetReader.sendPing(data, payloadSize)
packetReader.expectPingDropped()
}
fun clearApfMemory() = installProgram(ByteArray(caps.maximumApfProgramSize))
// APF integration is mostly broken before V
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
fun testPrefilledMemorySlotsV4() {
+ // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
+ // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
+ // should be turned on.
+ assume().that(getVsrApiLevel()).isAtLeast(34)
// Test v4 memory slots on both v4 and v6 interpreters.
assumeApfVersionSupportAtLeast(4)
clearApfMemory()
- val gen = ApfV4Generator(4)
+ val gen = ApfV4Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
// If not ICMPv6 Echo Reply -> PASS
gen.addPassIfNotIcmpv6EchoReply()
@@ -455,8 +509,13 @@
readProgram() // wait for install completion
// Trigger the program by sending a ping and waiting on the reply.
- val data = ByteArray(56).also { Random.nextBytes(it) }
- packetReader.sendPing(data)
+ val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ 68
+ } else {
+ 4
+ }
+ val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ packetReader.sendPing(data, payloadSize)
packetReader.expectPingReply()
val readResult = readProgram()
@@ -464,8 +523,103 @@
expect.withMessage("PROGRAM_SIZE").that(buffer.getInt()).isEqualTo(program.size)
expect.withMessage("RAM_LEN").that(buffer.getInt()).isEqualTo(caps.maximumApfProgramSize)
expect.withMessage("IPV4_HEADER_SIZE").that(buffer.getInt()).isEqualTo(0)
- // Ping packet (64) + IPv6 header (40) + ethernet header (14)
- expect.withMessage("PACKET_SIZE").that(buffer.getInt()).isEqualTo(64 + 40 + 14)
+ // Ping packet payload + ICMPv6 header (8) + IPv6 header (40) + ethernet header (14)
+ expect.withMessage("PACKET_SIZE").that(buffer.getInt()).isEqualTo(payloadSize + 8 + 40 + 14)
expect.withMessage("FILTER_AGE_SECONDS").that(buffer.getInt()).isLessThan(5)
}
+
+ // APF integration is mostly broken before V
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testFilterAgeIncreasesBetweenPackets() {
+ // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
+ // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
+ // should be turned on.
+ assume().that(getVsrApiLevel()).isAtLeast(34)
+ assumeApfVersionSupportAtLeast(4)
+ clearApfMemory()
+ val gen = ApfV4Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
+
+ // If not ICMPv6 Echo Reply -> PASS
+ gen.addPassIfNotIcmpv6EchoReply()
+
+ // Store all prefilled memory slots in counter region [500, 520)
+ val counterRegion = 500
+ gen.addLoadImmediate(R1, counterRegion)
+ gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS)
+ gen.addStoreData(R0, 0)
+
+ installProgram(gen.generate())
+ readProgram() // wait for install completion
+
+ val payloadSize = 56
+ val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ var buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */)
+ val filterAgeSecondsOrig = buffer.getInt()
+
+ Thread.sleep(5100)
+
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */)
+ val filterAgeSeconds = buffer.getInt()
+ // Assert that filter age has increased, but not too much.
+ val timeDiff = filterAgeSeconds - filterAgeSecondsOrig
+ assertThat(timeDiff).isAnyOf(5, 6)
+ }
+
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
+ @Test
+ fun testFilterAge16384thsIncreasesBetweenPackets() {
+ assumeApfVersionSupportAtLeast(6000)
+ clearApfMemory()
+ val gen = ApfV6Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
+
+ // If not ICMPv6 Echo Reply -> PASS
+ gen.addPassIfNotIcmpv6EchoReply()
+
+ // Store all prefilled memory slots in counter region [500, 520)
+ gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS)
+ gen.addStoreCounter(FILTER_AGE_16384THS, R0)
+
+ installProgram(gen.generate())
+ readProgram() // wait for install completion
+
+ val payloadSize = 56
+ val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ var apfRam = readProgram()
+ val filterAge16384thSecondsOrig =
+ ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS)
+
+ Thread.sleep(5000)
+
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ apfRam = readProgram()
+ val filterAge16384thSeconds = ApfCounterTracker.getCounterValue(apfRam, FILTER_AGE_16384THS)
+ val timeDiff = (filterAge16384thSeconds - filterAge16384thSecondsOrig)
+ // Expect the HAL plus ping latency to be less than 800ms.
+ val timeDiffLowerBound = (4.99 * 16384).toInt()
+ val timeDiffUpperBound = (5.81 * 16384).toInt()
+ // Assert that filter age has increased, but not too much.
+ assertThat(timeDiff).isGreaterThan(timeDiffLowerBound)
+ assertThat(timeDiff).isLessThan(timeDiffUpperBound)
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 5ed4696..21eb90f 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -36,11 +36,22 @@
import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_OEM_DENY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
import static android.net.ConnectivityManager.EXTRA_NETWORK;
import static android.net.ConnectivityManager.EXTRA_NETWORK_REQUEST;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
@@ -163,6 +174,7 @@
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
@@ -181,10 +193,11 @@
import android.util.Log;
import android.util.Range;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.RequiresDevice;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
import com.android.internal.util.ArrayUtils;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.CollectionUtils;
@@ -196,12 +209,12 @@
import com.android.testutils.CompatUtil;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DeviceConfigRule;
import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
-import com.android.testutils.SkipMainlinePresubmit;
import com.android.testutils.SkipPresubmit;
import com.android.testutils.TestHttpServer;
import com.android.testutils.TestNetworkTracker;
@@ -237,6 +250,7 @@
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@@ -297,6 +311,7 @@
// Airplane Mode BroadcastReceiver Timeout
private static final long AIRPLANE_MODE_CHANGE_TIMEOUT_MS = 10_000L;
+ private static final long CELL_DATA_AVAILABLE_TIMEOUT_MS = 120_000L;
// Timeout for applying uids allowed on restricted networks
private static final long APPLYING_UIDS_ALLOWED_ON_RESTRICTED_NETWORKS_TIMEOUT_MS = 3_000L;
@@ -322,6 +337,11 @@
private static final String TEST_HTTPS_URL_PATH = "/https_path";
private static final String TEST_HTTP_URL_PATH = "/http_path";
private static final String LOCALHOST_HOSTNAME = "localhost";
+ private static final String TEST_MODULE_NAME_OPTION = "test-module-name";
+ private static final String IP_ADDRESS_ECHO_URL_KEY = "IP_ADDRESS_ECHO_URL";
+ private static final List<String> ALLOWED_IP_ADDRESS_ECHO_URLS = Arrays.asList(
+ "https://google-ipv6test.appspot.com/ip.js?fmt=text",
+ "https://ipv6test.googleapis-cn.com/ip.js?fmt=text");
// Re-connecting to the AP, obtaining an IP address, revalidating can take a long time
private static final long WIFI_CONNECT_TIMEOUT_MS = 60_000L;
@@ -842,7 +862,7 @@
* Tests that connections can be opened on WiFi and cellphone networks,
* and that they are made from different IP addresses.
*/
- @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @AppModeFull(reason = "Cannot get WifiManager or access the SD card in instant app mode")
@Test
@RequiresDevice // Virtual devices use a single internet connection for all networks
public void testOpenConnection() throws Exception {
@@ -852,7 +872,8 @@
Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
Network cellNetwork = networkCallbackRule.requestCell();
// This server returns the requestor's IP address as the response body.
- URL url = new URL("http://google-ipv6test.appspot.com/ip.js?fmt=text");
+ String ipAddressEchoUrl = getIpAddressEchoUrlFromConfig();
+ URL url = new URL(ipAddressEchoUrl);
String wifiAddressString = httpGet(wifiNetwork, url);
String cellAddressString = httpGet(cellNetwork, url);
@@ -869,6 +890,19 @@
}
/**
+ * Gets IP address echo url from dynamic config.
+ */
+ private static String getIpAddressEchoUrlFromConfig() throws Exception {
+ Bundle instrumentationArgs = InstrumentationRegistry.getArguments();
+ String testModuleName = instrumentationArgs.getString(TEST_MODULE_NAME_OPTION);
+ // Get the DynamicConfig.xml contents and extract the ipv6 test URL.
+ DynamicConfigDeviceSide dynamicConfig = new DynamicConfigDeviceSide(testModuleName);
+ String ipAddressEchoUrl = dynamicConfig.getValue(IP_ADDRESS_ECHO_URL_KEY);
+ assertContains(ALLOWED_IP_ADDRESS_ECHO_URLS, ipAddressEchoUrl);
+ return ipAddressEchoUrl;
+ }
+
+ /**
* Performs a HTTP GET to the specified URL on the specified Network, and returns
* the response body decoded as UTF-8.
*/
@@ -1019,7 +1053,6 @@
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
- @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
public void testIsPrivateDnsBroken() throws InterruptedException {
final String invalidPrivateDnsServer = "invalidhostname.example.com";
final String goodPrivateDnsServer = "dns.google";
@@ -1224,13 +1257,14 @@
final IntentFilter filter = new IntentFilter();
filter.addAction(broadcastAction);
+ final CompletableFuture<NetworkRequest> requestFuture = new CompletableFuture<>();
final CompletableFuture<Network> networkFuture = new CompletableFuture<>();
final AtomicInteger receivedCount = new AtomicInteger(0);
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final NetworkRequest request = intent.getParcelableExtra(EXTRA_NETWORK_REQUEST);
- assertPendingIntentRequestMatches(request, secondRequest, useListen);
+ requestFuture.complete(request);
receivedCount.incrementAndGet();
networkFuture.complete(intent.getParcelableExtra(EXTRA_NETWORK));
}
@@ -1245,6 +1279,9 @@
} catch (TimeoutException e) {
throw new AssertionError("PendingIntent not received for " + secondRequest, e);
}
+ assertPendingIntentRequestMatches(
+ requestFuture.get(NETWORK_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS),
+ secondRequest, useListen);
// Sleep for a small amount of time to try to check that only one callback is ever
// received (so the first callback was really unregistered). This does not guarantee
@@ -2230,7 +2267,10 @@
// connectToCell only registers a request, it cannot / does not need to be called twice
mCtsNetUtils.ensureWifiConnected();
if (verifyWifi) waitForAvailable(wifiCb);
- if (supportTelephony) waitForAvailable(telephonyCb);
+ if (supportTelephony) {
+ telephonyCb.eventuallyExpect(
+ CallbackEntry.AVAILABLE, CELL_DATA_AVAILABLE_TIMEOUT_MS);
+ }
} finally {
// Restore the previous state of airplane mode and permissions:
runShellCommand("cmd connectivity airplane-mode "
@@ -2472,8 +2512,11 @@
}
}
+ // On V+, ConnectivityService generates blockedReasons based on bpf map contents even if the
+ // otherUid does not exist on device. So if allowlist chain (e.g. background chain) is enabled,
+ // blockedReasons for otherUid will not be BLOCKED_REASON_NONE.
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
- @Test
+ @Test @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void testBlockedStatusCallback() throws Exception {
// Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
// shims, and @IgnoreUpTo does not check that.
@@ -3713,6 +3756,260 @@
Process.myUid() + 1, EXPECT_OPEN);
}
+ private int getBlockedReason(final int chain) {
+ switch(chain) {
+ case FIREWALL_CHAIN_DOZABLE:
+ return BLOCKED_REASON_DOZE;
+ case FIREWALL_CHAIN_POWERSAVE:
+ return BLOCKED_REASON_BATTERY_SAVER;
+ case FIREWALL_CHAIN_RESTRICTED:
+ return BLOCKED_REASON_RESTRICTED_MODE;
+ case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+ return BLOCKED_REASON_LOW_POWER_STANDBY;
+ case FIREWALL_CHAIN_BACKGROUND:
+ return BLOCKED_REASON_APP_BACKGROUND;
+ case FIREWALL_CHAIN_STANDBY:
+ return BLOCKED_REASON_APP_STANDBY;
+ case FIREWALL_CHAIN_METERED_DENY_USER:
+ return BLOCKED_METERED_REASON_USER_RESTRICTED;
+ case FIREWALL_CHAIN_METERED_DENY_ADMIN:
+ return BLOCKED_METERED_REASON_ADMIN_DISABLED;
+ case FIREWALL_CHAIN_OEM_DENY_1:
+ case FIREWALL_CHAIN_OEM_DENY_2:
+ case FIREWALL_CHAIN_OEM_DENY_3:
+ return BLOCKED_REASON_OEM_DENY;
+ default:
+ throw new IllegalArgumentException(
+ "Failed to find blockedReasons for chain: " + chain);
+ }
+ }
+
+ private void doTestBlockedReasons_setUidFirewallRule(final int chain, final boolean metered)
+ throws Exception {
+ assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
+
+ // Store current Wi-Fi metered value and update metered value
+ final Network currentWifiNetwork = mCtsNetUtils.ensureWifiConnected();
+ final NetworkCapabilities wifiNetworkCapabilities = callWithShellPermissionIdentity(
+ () -> mCm.getNetworkCapabilities(currentWifiNetwork));
+ final String ssid = unquoteSSID(wifiNetworkCapabilities.getSsid());
+ final boolean oldMeteredValue = wifiNetworkCapabilities.isMetered();
+ final Network wifiNetwork =
+ setWifiMeteredStatusAndWait(ssid, metered, true /* waitForValidation */);
+
+ // Store current firewall chains status. This test operates on the chain that is passed in,
+ // but also always operates on FIREWALL_CHAIN_METERED_DENY_USER to ensure that metered
+ // chains are tested as well.
+ final int myUid = Process.myUid();
+ final boolean wasChainEnabled = runWithShellPermissionIdentity(
+ () -> mCm.getFirewallChainEnabled(chain), NETWORK_SETTINGS);
+ final int previousFirewallRule = runWithShellPermissionIdentity(
+ () -> mCm.getUidFirewallRule(chain, myUid));
+ final int previousMeteredDenyFirewallRule = runWithShellPermissionIdentity(
+ () -> mCm.getUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, myUid));
+
+ final DetailedBlockedStatusCallback cb = new DetailedBlockedStatusCallback();
+ networkCallbackRule.requestNetwork(makeWifiNetworkRequest(), cb);
+ testAndCleanup(() -> {
+ int blockedReasonsWithoutChain = BLOCKED_REASON_NONE;
+ int blockedReasonsWithChain = getBlockedReason(chain);
+ int blockedReasonsWithChainAndLockDown =
+ getBlockedReason(chain) | BLOCKED_REASON_LOCKDOWN_VPN;
+ if (metered) {
+ blockedReasonsWithoutChain |= BLOCKED_METERED_REASON_USER_RESTRICTED;
+ blockedReasonsWithChain |= BLOCKED_METERED_REASON_USER_RESTRICTED;
+ blockedReasonsWithChainAndLockDown |= BLOCKED_METERED_REASON_USER_RESTRICTED;
+ }
+
+ // Set RULE_DENY on target chain and metered deny chain
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, true /* enable */);
+ mCm.setUidFirewallRule(chain, myUid, FIREWALL_RULE_DENY);
+ mCm.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, myUid,
+ FIREWALL_RULE_DENY);
+ }, NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork, blockedReasonsWithChain);
+
+ // Set VPN lockdown
+ final Range<Integer> myUidRange = new Range<>(myUid, myUid);
+ runWithShellPermissionIdentity(() -> setRequireVpnForUids(
+ true /* requireVpn */, List.of(myUidRange)), NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork,
+ blockedReasonsWithChainAndLockDown);
+
+ // Unset VPN lockdown
+ runWithShellPermissionIdentity(() -> setRequireVpnForUids(
+ false /* requireVpn */, List.of(myUidRange)), NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork, blockedReasonsWithChain);
+
+ // Set RULE_ALLOW on target chain
+ runWithShellPermissionIdentity(
+ () -> mCm.setUidFirewallRule(chain, myUid, FIREWALL_RULE_ALLOW),
+ NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork, blockedReasonsWithoutChain);
+
+ // Set RULE_ALLOW on metered deny chain
+ runWithShellPermissionIdentity(() -> mCm.setUidFirewallRule(
+ FIREWALL_CHAIN_METERED_DENY_USER, myUid, FIREWALL_RULE_ALLOW),
+ NETWORK_SETTINGS);
+ if (metered) {
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork, BLOCKED_REASON_NONE);
+ }
+ }, /* cleanup */ () -> {
+ setWifiMeteredStatusAndWait(ssid, oldMeteredValue, false /* waitForValidation */);
+ }, /* cleanup */ () -> {
+ mCm.unregisterNetworkCallback(cb);
+ }, /* cleanup */ () -> {
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, wasChainEnabled);
+ try {
+ mCm.setUidFirewallRule(chain, myUid, previousFirewallRule);
+ } catch (IllegalStateException ignored) {
+ // Removing match causes an exception when the rule entry for the uid does
+ // not exist. But this is fine and can be ignored.
+ }
+ try {
+ mCm.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, myUid,
+ previousMeteredDenyFirewallRule);
+ } catch (IllegalStateException ignored) {
+ // Removing match causes an exception when the rule entry for the uid does
+ // not exist. But this is fine and can be ignored.
+ }
+ }, NETWORK_SETTINGS);
+ });
+ }
+
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+ public void testBlockedReasons_setUidFirewallRule() throws Exception {
+ doTestBlockedReasons_setUidFirewallRule(FIREWALL_CHAIN_DOZABLE, true /* metered */);
+ doTestBlockedReasons_setUidFirewallRule(FIREWALL_CHAIN_STANDBY, false /* metered */);
+ }
+
+ private void doTestBlockedReasons_setFirewallChainEnabled(final int chain) {
+ // Store current firewall chains status.
+ final int myUid = Process.myUid();
+ // TODO(b/342508466): Use runAsShell
+ final boolean wasChainEnabled = runWithShellPermissionIdentity(
+ () -> mCm.getFirewallChainEnabled(chain), NETWORK_SETTINGS);
+ final int previousFirewallRule = runWithShellPermissionIdentity(
+ () -> mCm.getUidFirewallRule(chain, myUid), NETWORK_SETTINGS);
+
+ final DetailedBlockedStatusCallback cb = new DetailedBlockedStatusCallback();
+ networkCallbackRule.registerDefaultNetworkCallback(cb);
+ final Network network = cb.expect(CallbackEntry.AVAILABLE).getNetwork();
+ testAndCleanup(() -> {
+ // Disable chain and set RULE_DENY on target chain
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ mCm.setUidFirewallRule(chain, myUid, FIREWALL_RULE_DENY);
+ }, NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+
+ // Enable chain
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, true /* enable */);
+ }, NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(network, getBlockedReason(chain));
+
+ // Disable chain
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ }, NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+ }, /* cleanup */ () -> {
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, wasChainEnabled);
+ try {
+ mCm.setUidFirewallRule(chain, myUid, previousFirewallRule);
+ } catch (IllegalStateException ignored) {
+ // Removing match causes an exception when the rule entry for the uid does
+ // not exist. But this is fine and can be ignored.
+ }
+ }, NETWORK_SETTINGS);
+ });
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+ public void testBlockedReasons_setFirewallChainEnabled() {
+ doTestBlockedReasons_setFirewallChainEnabled(FIREWALL_CHAIN_POWERSAVE);
+ doTestBlockedReasons_setFirewallChainEnabled(FIREWALL_CHAIN_OEM_DENY_1);
+ }
+
+ private void doTestBlockedReasons_replaceFirewallChain(
+ final int chain, final boolean isAllowList) {
+ // Store current firewall chains status.
+ final int myUid = Process.myUid();
+ final boolean wasChainEnabled = runWithShellPermissionIdentity(
+ () -> mCm.getFirewallChainEnabled(chain), NETWORK_SETTINGS);
+ final int previousFirewallRule = runWithShellPermissionIdentity(
+ () -> mCm.getUidFirewallRule(chain, myUid), NETWORK_SETTINGS);
+
+ final DetailedBlockedStatusCallback cb = new DetailedBlockedStatusCallback();
+ networkCallbackRule.registerDefaultNetworkCallback(cb);
+ final Network network = cb.expect(CallbackEntry.AVAILABLE).getNetwork();
+ testAndCleanup(() -> {
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+
+ // Remove uid from the target chain and enable chain
+ runWithShellPermissionIdentity(() -> {
+ // Note that this removes *all* UIDs from the chain, not just the UID that is
+ // being tested. This is probably OK since FIREWALL_CHAIN_OEM_DENY_2 is unused
+ // in AOSP and FIREWALL_CHAIN_BACKGROUND is probably empty when running this
+ // test (since nothing is in the foreground).
+ //
+ // TODO(b/342508466): add a getFirewallUidChainContents or similar method to fetch
+ // chain contents, and update this test to use it.
+ mCm.replaceFirewallChain(chain, new int[0]);
+ mCm.setFirewallChainEnabled(chain, true /* enable */);
+ }, NETWORK_SETTINGS);
+
+ if (isAllowList) {
+ cb.eventuallyExpectBlockedStatusCallback(network, getBlockedReason(chain));
+ } else {
+ cb.assertNoBlockedStatusCallback();
+ }
+
+ // Put uid on the target chain
+ runWithShellPermissionIdentity(
+ () -> mCm.replaceFirewallChain(chain, new int[]{myUid}), NETWORK_SETTINGS);
+
+ if (isAllowList) {
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+ } else {
+ cb.eventuallyExpectBlockedStatusCallback(network, getBlockedReason(chain));
+ }
+
+ // Remove uid from the target chain
+ runWithShellPermissionIdentity(
+ () -> mCm.replaceFirewallChain(chain, new int[0]), NETWORK_SETTINGS);
+
+ if (isAllowList) {
+ cb.eventuallyExpectBlockedStatusCallback(network, getBlockedReason(chain));
+ } else {
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+ }
+ }, /* cleanup */ () -> {
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, wasChainEnabled);
+ try {
+ mCm.setUidFirewallRule(chain, myUid, previousFirewallRule);
+ } catch (IllegalStateException ignored) {
+ // Removing match causes an exception when the rule entry for the uid does
+ // not exist. But this is fine and can be ignored.
+ }
+ }, NETWORK_SETTINGS);
+ });
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+ public void testBlockedReasons_replaceFirewallChain() {
+ doTestBlockedReasons_replaceFirewallChain(
+ FIREWALL_CHAIN_BACKGROUND, true /* isAllowChain */);
+ doTestBlockedReasons_replaceFirewallChain(
+ FIREWALL_CHAIN_OEM_DENY_2, false /* isAllowChain */);
+ }
+
private void assumeTestSApis() {
// Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
// shims, and @IgnoreUpTo does not check that.
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 6fa2812..61ebd8f 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -303,18 +303,6 @@
fun expectOnAvailable(timeout: Long = TIMEOUT_MS): String {
return available.get(timeout, TimeUnit.MILLISECONDS)
}
-
- fun expectOnUnavailable() {
- // Assert that the future fails with the IllegalStateException from the
- // completeExceptionally() call inside onUnavailable.
- assertFailsWith(IllegalStateException::class) {
- try {
- available.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
- } catch (e: ExecutionException) {
- throw e.cause!!
- }
- }
- }
}
private class EthernetOutcomeReceiver :
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 5b53839..ff10e1a 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -19,19 +19,19 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static com.android.testutils.DevSdkIgnoreRuleKt.VANILLA_ICE_CREAM;
-
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
@@ -66,9 +66,9 @@
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -512,30 +512,20 @@
assertArrayEquals(netCapabilities, nr.getCapabilities());
}
- @Test @IgnoreUpTo(VANILLA_ICE_CREAM) @Ignore("b/338200742")
+ // Default capabilities and default forbidden capabilities must not be changed on U- because
+ // this could cause the system server crash when there is a module rollback (b/313030307)
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R) @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void testDefaultCapabilities() {
final NetworkRequest defaultNR = new NetworkRequest.Builder().build();
- assertTrue(defaultNR.hasForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK));
- assertFalse(defaultNR.hasCapability(NET_CAPABILITY_LOCAL_NETWORK));
+
+ assertEquals(4, defaultNR.getCapabilities().length);
+ assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ assertTrue(defaultNR.hasCapability(NET_CAPABILITY_TRUSTED));
assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_VPN));
+ assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
- final NetworkCapabilities emptyNC =
- NetworkCapabilities.Builder.withoutDefaultCapabilities().build();
- assertFalse(defaultNR.canBeSatisfiedBy(emptyNC));
-
- // defaultNC represent the capabilities of a network agent, so they must not contain
- // forbidden capabilities by default.
- final NetworkCapabilities defaultNC = new NetworkCapabilities.Builder().build();
- assertArrayEquals(new int[0], defaultNC.getForbiddenCapabilities());
- // A default NR can be satisfied by default NC.
- assertTrue(defaultNR.canBeSatisfiedBy(defaultNC));
-
- // Conversely, network requests have forbidden capabilities by default to manage
- // backward compatibility, so test that these forbidden capabilities are in place.
- // Starting in V, NET_CAPABILITY_LOCAL_NETWORK is introduced but is not seen by
- // default, thanks to a default forbidden capability in NetworkRequest.
- defaultNC.addCapability(NET_CAPABILITY_LOCAL_NETWORK);
- assertFalse(defaultNR.canBeSatisfiedBy(defaultNC));
+ assertEquals(0, defaultNR.getForbiddenCapabilities().length);
}
@Test
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
index 1b1f367..284fcae 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
@@ -28,7 +28,9 @@
import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.NsdDiscoveryRecord
import com.android.testutils.TapPacketReader
+import com.android.testutils.pollForQuery
import com.android.testutils.tryTest
import java.util.Random
import kotlin.test.assertEquals
@@ -72,7 +74,7 @@
tryTest {
downstreamIface = createTestInterface()
- val iface = tetheredInterface
+ val iface = mTetheredInterfaceRequester.getInterface()
assertEquals(iface, downstreamIface?.interfaceName)
val request = TetheringRequest.Builder(TETHERING_ETHERNET)
.setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build()
@@ -115,7 +117,7 @@
tryTest {
downstreamIface = createTestInterface()
- val iface = tetheredInterface
+ val iface = mTetheredInterfaceRequester.getInterface()
assertEquals(iface, downstreamIface?.interfaceName)
val localAddr = LinkAddress("192.0.2.3/28")
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 6394599..be80787 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -39,19 +39,6 @@
import android.net.TestNetworkManager
import android.net.TestNetworkSpecifier
import android.net.connectivity.ConnectivityCompatChanges
-import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
-import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
-import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
-import android.net.cts.NsdDiscoveryRecord.DiscoveryEvent.ServiceLost
-import android.net.cts.NsdRegistrationRecord.RegistrationEvent.RegistrationFailed
-import android.net.cts.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
-import android.net.cts.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
-import android.net.cts.NsdResolveRecord.ResolveEvent.ResolutionStopped
-import android.net.cts.NsdResolveRecord.ResolveEvent.ServiceResolved
-import android.net.cts.NsdResolveRecord.ResolveEvent.StopResolutionFailed
-import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdated
-import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdatedLost
-import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.UnregisterCallbackSucceeded
import android.net.cts.util.CtsNetUtils
import android.net.nsd.DiscoveryRequest
import android.net.nsd.NsdManager
@@ -92,9 +79,29 @@
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.DeviceConfigRule
import com.android.testutils.NSResponder
+import com.android.testutils.NsdDiscoveryRecord
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
+import com.android.testutils.NsdDiscoveryRecord.DiscoveryEvent.ServiceLost
+import com.android.testutils.NsdEvent
+import com.android.testutils.NsdRecord
+import com.android.testutils.NsdRegistrationRecord
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.RegistrationFailed
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
+import com.android.testutils.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
+import com.android.testutils.NsdResolveRecord
+import com.android.testutils.NsdResolveRecord.ResolveEvent.ResolutionStopped
+import com.android.testutils.NsdResolveRecord.ResolveEvent.ServiceResolved
+import com.android.testutils.NsdResolveRecord.ResolveEvent.StopResolutionFailed
+import com.android.testutils.NsdServiceInfoCallbackRecord
+import com.android.testutils.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdated
+import com.android.testutils.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdatedLost
+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.TestDnsPacket
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
import com.android.testutils.TestableNetworkCallback
@@ -102,6 +109,11 @@
import com.android.testutils.assertEmpty
import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk30
import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk33
+import com.android.testutils.pollForAdvertisement
+import com.android.testutils.pollForMdnsPacket
+import com.android.testutils.pollForProbe
+import com.android.testutils.pollForQuery
+import com.android.testutils.pollForReply
import com.android.testutils.runAsShell
import com.android.testutils.tryTest
import com.android.testutils.waitForIdle
diff --git a/tests/cts/netpermission/internetpermission/Android.bp b/tests/cts/netpermission/internetpermission/Android.bp
index 7d5ca2f..e0424ac 100644
--- a/tests/cts/netpermission/internetpermission/Android.bp
+++ b/tests/cts/netpermission/internetpermission/Android.bp
@@ -31,4 +31,5 @@
"general-tests",
],
host_required: ["net-tests-utils-host-common"],
+ sdk_version: "test_current",
}
diff --git a/tests/cts/netpermission/updatestatspermission/Android.bp b/tests/cts/netpermission/updatestatspermission/Android.bp
index 2fde1ce..689ce74 100644
--- a/tests/cts/netpermission/updatestatspermission/Android.bp
+++ b/tests/cts/netpermission/updatestatspermission/Android.bp
@@ -19,11 +19,17 @@
android_test {
name: "CtsNetTestCasesUpdateStatsPermission",
- defaults: ["cts_defaults"],
+ defaults: [
+ "cts_defaults",
+ "framework-connectivity-test-defaults",
+ ],
srcs: ["src/**/*.java"],
-
- static_libs: ["ctstestrunner-axt"],
+ platform_apis: true,
+ static_libs: [
+ "ctstestrunner-axt",
+ "net-tests-utils",
+ ],
// Tag this module as a cts test artifact
test_suites: [
diff --git a/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java b/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
index bea843c..56bad31 100644
--- a/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
+++ b/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
@@ -16,6 +16,10 @@
package android.net.cts.networkpermission.updatestatspermission;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -25,6 +29,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.build.SdkLevel;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -67,6 +73,11 @@
out.write(buf);
out.close();
socket.close();
+ // Clear TrafficStats cache is needed to avoid rate-limit caching for
+ // TrafficStats API results on V+ devices.
+ if (SdkLevel.isAtLeastV()) {
+ runAsShell(NETWORK_SETTINGS, () -> TrafficStats.clearRateLimitCaches());
+ }
long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter + " delta="
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index d2e46af..06bdca6 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -27,6 +27,7 @@
import android.net.ConnectivityManager
import android.net.IDnsResolver
import android.net.INetd
+import android.net.INetd.PERMISSION_INTERNET
import android.net.LinkProperties
import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
@@ -225,6 +226,9 @@
override fun getSystemProperties() = mock(MockableSystemProperties::class.java)
override fun makeNetIdManager() = TestNetIdManager()
override fun getBpfNetMaps(context: Context?, netd: INetd?) = mock(BpfNetMaps::class.java)
+ .also {
+ doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt())
+ }
override fun isChangeEnabled(changeId: Long, uid: Int) = true
override fun makeMultinetworkPolicyTracker(
diff --git a/tests/mts/Android.bp b/tests/mts/Android.bp
index 336be2e..c118d0a 100644
--- a/tests/mts/Android.bp
+++ b/tests/mts/Android.bp
@@ -31,6 +31,8 @@
header_libs: [
"bpf_headers",
],
+ version_script: ":connectivity_mainline_test_map",
+ stl: "libc++_static",
static_libs: [
"libbase",
"libmodules-utils-build",
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index 51a4eca..29f5cd2 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -68,6 +68,8 @@
TETHERING "map_offload_tether_upstream6_map",
TETHERING "map_test_bitmap",
TETHERING "map_test_tether_downstream6_map",
+ TETHERING "map_test_tether2_downstream6_map",
+ TETHERING "map_test_tether3_downstream6_map",
TETHERING "prog_offload_schedcls_tether_downstream4_ether",
TETHERING "prog_offload_schedcls_tether_downstream4_rawip",
TETHERING "prog_offload_schedcls_tether_downstream6_ether",
@@ -141,6 +143,27 @@
NETD "map_netd_packet_trace_ringbuf",
};
+// Provided by *current* mainline module for V+ devices
+static const set<string> MAINLINE_FOR_V_PLUS = {
+ NETD "prog_netd_connect4_inet4_connect",
+ NETD "prog_netd_connect6_inet6_connect",
+ NETD "prog_netd_recvmsg4_udp4_recvmsg",
+ NETD "prog_netd_recvmsg6_udp6_recvmsg",
+ NETD "prog_netd_sendmsg4_udp4_sendmsg",
+ NETD "prog_netd_sendmsg6_udp6_sendmsg",
+};
+
+// Provided by *current* mainline module for V+ devices with 5.4+ kernels
+static const set<string> MAINLINE_FOR_V_5_4_PLUS = {
+ NETD "prog_netd_getsockopt_prog",
+ NETD "prog_netd_setsockopt_prog",
+};
+
+// Provided by *current* mainline module for U+ 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());
}
@@ -188,6 +211,9 @@
// V requires Linux Kernel 4.19+, but nothing (as yet) added or removed in V.
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/tests/native/connectivity_native_test/Android.bp b/tests/native/connectivity_native_test/Android.bp
index 2f66d17..c5088c6 100644
--- a/tests/native/connectivity_native_test/Android.bp
+++ b/tests/native/connectivity_native_test/Android.bp
@@ -17,8 +17,9 @@
"connectivity_native_test.cpp",
],
header_libs: ["bpf_connectivity_headers"],
+ version_script: ":connectivity_mainline_test_map",
+ stl: "libc++_static",
shared_libs: [
- "libbase",
"libbinder_ndk",
"liblog",
"libnetutils",
@@ -26,6 +27,7 @@
],
static_libs: [
"connectivity_native_aidl_interface-lateststable-ndk",
+ "libbase",
"libcutils",
"libmodules-utils-build",
"libutils",
diff --git a/tests/native/utilities/firewall.h b/tests/native/utilities/firewall.h
index b3d69bf..a5cb0b9 100644
--- a/tests/native/utilities/firewall.h
+++ b/tests/native/utilities/firewall.h
@@ -18,6 +18,7 @@
#pragma once
#include <android-base/thread_annotations.h>
+#define BPF_MAP_LOCKLESS_FOR_TEST
#include <bpf/BpfMap.h>
#include "netd.h"
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index b71a46f..9a77c89 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -52,6 +52,7 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -72,6 +73,7 @@
import android.os.Messenger;
import android.os.Process;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.internal.util.test.BroadcastInterceptingContext;
@@ -83,6 +85,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -240,7 +243,7 @@
// register callback
when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
- anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(request);
+ anyInt(), anyInt(), any(), nullable(String.class), anyInt())).thenReturn(request);
manager.requestNetwork(request, callback, handler);
// callback triggers
@@ -269,7 +272,7 @@
// register callback
when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
- anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req1);
+ anyInt(), anyInt(), any(), nullable(String.class), anyInt())).thenReturn(req1);
manager.requestNetwork(req1, callback, handler);
// callback triggers
@@ -287,7 +290,7 @@
// callback can be registered again
when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(),
- anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req2);
+ anyInt(), anyInt(), any(), nullable(String.class), anyInt())).thenReturn(req2);
manager.requestNetwork(req2, callback, handler);
// callback triggers
@@ -311,7 +314,7 @@
when(mCtx.getApplicationInfo()).thenReturn(info);
when(mService.requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(),
- anyInt(), any(), nullable(String.class))).thenReturn(request);
+ anyInt(), any(), nullable(String.class), anyInt())).thenReturn(request);
Handler handler = new Handler(Looper.getMainLooper());
manager.requestNetwork(request, callback, handler);
@@ -403,15 +406,15 @@
manager.requestNetwork(request, callback);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
// Verify that register network callback does not calls requestNetwork at all.
manager.registerNetworkCallback(request, callback);
verify(mService, never()).requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(),
- anyInt(), anyInt(), any(), any());
+ anyInt(), anyInt(), any(), any(), anyInt());
verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
@@ -419,24 +422,24 @@
manager.registerDefaultNetworkCallback(callback);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
manager.registerDefaultNetworkCallbackForUid(42, callback, handler);
verify(mService).requestNetwork(eq(42), eq(null),
eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
manager.requestBackgroundNetwork(request, callback, handler);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
manager.registerSystemDefaultNetworkCallback(callback, handler);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null),
eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
- eq(testPkgName), eq(testAttributionTag));
+ eq(testPkgName), eq(testAttributionTag), anyInt());
reset(mService);
}
@@ -516,16 +519,154 @@
+ " attempts", ref.get());
}
- private <T> void mockService(Class<T> clazz, String name, T service) {
- doReturn(service).when(mCtx).getSystemService(name);
- doReturn(name).when(mCtx).getSystemServiceName(clazz);
+ @Test
+ public void testDeclaredMethodsFlag_requestWithMixedMethods_RegistrationFlagsMatch()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
- // If the test suite uses the inline mock maker library, such as for coverage tests,
- // then the final version of getSystemService must also be mocked, as the real
- // method will not be called by the test and null object is returned since no mock.
- // Otherwise, mocking a final method will fail the test.
- if (mCtx.getSystemService(clazz) == null) {
- doReturn(service).when(mCtx).getSystemService(clazz);
- }
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ final NetworkCallback callback1 = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onPreCheck(@NonNull Network network) {}
+ @Override
+ public void onAvailable(@NonNull Network network) {}
+ @Override
+ public void onLost(@NonNull Network network) {}
+ @Override
+ public void onCapabilitiesChanged(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities) {}
+ @Override
+ public void onLocalNetworkInfoChanged(@NonNull Network network,
+ @NonNull LocalNetworkInfo localNetworkInfo) {}
+ @Override
+ public void onNetworkResumed(@NonNull Network network) {}
+ @Override
+ public void onBlockedStatusChanged(@NonNull Network network, int blocked) {}
+ };
+ manager.requestNetwork(request, callback1);
+
+ final InOrder inOrder = inOrder(mService);
+ inOrder.verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_PRECHECK
+ | 1 << ConnectivityManager.CALLBACK_AVAILABLE
+ | 1 << ConnectivityManager.CALLBACK_LOST
+ | 1 << ConnectivityManager.CALLBACK_CAP_CHANGED
+ | 1 << ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED
+ | 1 << ConnectivityManager.CALLBACK_RESUMED
+ | 1 << ConnectivityManager.CALLBACK_BLK_CHANGED));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_listenWithMixedMethods_RegistrationFlagsMatch()
+ throws Exception {
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+
+ final NetworkCallback callback2 = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onLosing(@NonNull Network network, int maxMsToLive) {}
+ @Override
+ public void onUnavailable() {}
+ @Override
+ public void onLinkPropertiesChanged(@NonNull Network network,
+ @NonNull LinkProperties linkProperties) {}
+ @Override
+ public void onNetworkSuspended(@NonNull Network network) {}
+ };
+ manager.registerNetworkCallback(request, callback2);
+ // Call a second time with the same callback to exercise caching
+ manager.registerNetworkCallback(request, callback2);
+
+ verify(mService, times(2)).listenForNetwork(
+ any(), any(), any(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_LOSING
+ // AVAILABLE calls IP_CHANGED and SUSPENDED so it gets added
+ | 1 << ConnectivityManager.CALLBACK_AVAILABLE
+ | 1 << ConnectivityManager.CALLBACK_UNAVAIL
+ | 1 << ConnectivityManager.CALLBACK_IP_CHANGED
+ | 1 << ConnectivityManager.CALLBACK_SUSPENDED));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_requestWithHiddenAvailableCallback_RegistrationFlagsMatch()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+
+ final NetworkCallback hiddenOnAvailableCb = new ConnectivityManager.NetworkCallback() {
+ // This overload is @hide but might still be used by (bad) apps
+ @Override
+ public void onAvailable(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities,
+ @NonNull LinkProperties linkProperties, boolean blocked) {}
+ };
+ manager.registerDefaultNetworkCallback(hiddenOnAvailableCb);
+
+ verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_AVAILABLE));
+ }
+
+ public static class NetworkCallbackWithOnLostOnly extends NetworkCallback {
+ @Override
+ public void onLost(@NonNull Network network) {}
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_requestWithoutAvailableCallback_RegistrationFlagsMatch()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final NetworkCallback noOnAvailableCb = new NetworkCallbackWithOnLostOnly();
+ manager.registerSystemDefaultNetworkCallback(noOnAvailableCb, handler);
+
+ verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ eq(1 << ConnectivityManager.CALLBACK_LOST));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_listenWithMock_OptimizationDisabled()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ manager.registerNetworkCallback(request, mock(NetworkCallbackWithOnLostOnly.class),
+ handler);
+
+ verify(mService).listenForNetwork(
+ any(), any(), any(), anyInt(), any(), any(),
+ // Mock that does not call the constructor -> do not use the optimization
+ eq(~0));
+ }
+
+ @Test
+ public void testDeclaredMethodsFlag_requestWitNoCallback_OptimizationDisabled()
+ throws Exception {
+ doReturn(ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS)
+ .when(mService).getEnabledConnectivityManagerFeatures();
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ final NetworkCallback noCallbackAtAll = new ConnectivityManager.NetworkCallback() {};
+ manager.requestBackgroundNetwork(request, noCallbackAtAll, handler);
+
+ verify(mService).requestNetwork(
+ anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), any(), any(),
+ // No callbacks overridden -> do not use the optimization
+ eq(~0));
}
}
diff --git a/tests/unit/java/android/net/NetworkCallbackFlagsTest.kt b/tests/unit/java/android/net/NetworkCallbackFlagsTest.kt
new file mode 100644
index 0000000..af06a64
--- /dev/null
+++ b/tests/unit/java/android/net/NetworkCallbackFlagsTest.kt
@@ -0,0 +1,212 @@
+/*
+ * 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 android.net
+
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.ConnectivityManager.NetworkCallbackMethodsHolder
+import android.os.Build
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doCallRealMethod
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.mockingDetails
+
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@RunWith(DevSdkIgnoreRunner::class)
+class NetworkCallbackFlagsTest {
+
+ // To avoid developers forgetting to update NETWORK_CB_METHODS when modifying NetworkCallbacks,
+ // or using wrong values, calculate it from annotations here and verify that it matches.
+ // This avoids the runtime cost of reflection, but still ensures that the list is correct.
+ @Test
+ fun testNetworkCallbackMethods_calculateFromAnnotations_matchesHardcodedList() {
+ val calculatedMethods = getNetworkCallbackMethodsFromAnnotations()
+ assertEquals(
+ calculatedMethods.toSet(),
+ NetworkCallbackMethodsHolder.NETWORK_CB_METHODS.map {
+ NetworkCallbackMethodWithEquals(
+ it.mName,
+ it.mParameterTypes.toList(),
+ callbacksCallingThisMethod = it.mCallbacksCallingThisMethod
+ )
+ }.toSet()
+ )
+ }
+
+ data class NetworkCallbackMethodWithEquals(
+ val name: String,
+ val parameterTypes: List<Class<*>>,
+ val callbacksCallingThisMethod: Int
+ )
+
+ data class NetworkCallbackMethodBuilder(
+ val name: String,
+ val parameterTypes: List<Class<*>>,
+ val isFinal: Boolean,
+ val methodId: Int,
+ val mayCall: Set<Int>?,
+ var callbacksCallingThisMethod: Int
+ ) {
+ fun build() = NetworkCallbackMethodWithEquals(
+ name,
+ parameterTypes,
+ callbacksCallingThisMethod
+ )
+ }
+
+ /**
+ * Build [NetworkCallbackMethodsHolder.NETWORK_CB_METHODS] from [NetworkCallback] annotations.
+ */
+ private fun getNetworkCallbackMethodsFromAnnotations(): List<NetworkCallbackMethodWithEquals> {
+ val parsedMethods = mutableListOf<NetworkCallbackMethodBuilder>()
+ val methods = NetworkCallback::class.java.declaredMethods
+ methods.forEach { method ->
+ val cb = method.getAnnotation(
+ NetworkCallback.FilteredCallback::class.java
+ ) ?: return@forEach
+ val callbacksCallingThisMethod = if (cb.calledByCallbackId == 0) {
+ 0
+ } else {
+ 1 shl cb.calledByCallbackId
+ }
+ parsedMethods.add(
+ NetworkCallbackMethodBuilder(
+ method.name,
+ method.parameterTypes.toList(),
+ Modifier.isFinal(method.modifiers),
+ cb.methodId,
+ cb.mayCall.toSet(),
+ callbacksCallingThisMethod
+ )
+ )
+ }
+
+ // Propagate callbacksCallingThisMethod for transitive calls
+ do {
+ var hadChange = false
+ parsedMethods.forEach { caller ->
+ parsedMethods.forEach { callee ->
+ if (caller.mayCall?.contains(callee.methodId) == true) {
+ // Callbacks that call the caller also cause calls to the callee. So
+ // callbacksCallingThisMethod for the callee should include
+ // callbacksCallingThisMethod from the caller.
+ val newValue =
+ caller.callbacksCallingThisMethod or callee.callbacksCallingThisMethod
+ hadChange = hadChange || callee.callbacksCallingThisMethod != newValue
+ callee.callbacksCallingThisMethod = newValue
+ }
+ }
+ }
+ } while (hadChange)
+
+ // Final methods may affect the flags for transitive calls, but cannot be overridden, so do
+ // not need to be in the list (no overridden method in NetworkCallback will match them).
+ return parsedMethods.filter { !it.isFinal }.map { it.build() }
+ }
+
+ @Test
+ fun testMethodsAreAnnotated() {
+ val annotations = NetworkCallback::class.java.declaredMethods.mapNotNull { method ->
+ if (!Modifier.isPublic(method.modifiers) && !Modifier.isProtected(method.modifiers)) {
+ return@mapNotNull null
+ }
+ val annotation = method.getAnnotation(NetworkCallback.FilteredCallback::class.java)
+ assertNotNull(annotation, "$method is missing the @FilteredCallback annotation")
+ return@mapNotNull annotation
+ }
+
+ annotations.groupingBy { it.methodId }.eachCount().forEach { (methodId, cnt) ->
+ assertEquals(1, cnt, "Method ID $methodId is used more than once in @FilteredCallback")
+ }
+ }
+
+ @Test
+ fun testObviousCalleesAreInAnnotation() {
+ NetworkCallback::class.java.declaredMethods.forEach { method ->
+ val annotation = method.getAnnotation(NetworkCallback.FilteredCallback::class.java)
+ ?: return@forEach
+ val missingFlags = getObviousCallees(method).toMutableSet().apply {
+ removeAll(annotation.mayCall.toSet())
+ }
+ val msg = "@FilteredCallback on $method is missing flags " +
+ "$missingFlags in mayCall. There may be other " +
+ "calls that are not detected if they are done conditionally."
+ assertEquals(emptySet(), missingFlags, msg)
+ }
+ }
+
+ /**
+ * Invoke the specified NetworkCallback method with mock arguments, return a set of transitively
+ * called methods.
+ *
+ * This provides an idea of which methods are transitively called by the specified method. It's
+ * not perfect as some callees could be called or not depending on the exact values of the mock
+ * arguments that are passed in (for example, onAvailable calls onNetworkSuspended only if the
+ * capabilities lack the NOT_SUSPENDED capability), but it should catch obvious forgotten calls.
+ */
+ private fun getObviousCallees(method: Method): Set<Int> {
+ // Create a mock NetworkCallback that mocks all methods except the one specified by the
+ // caller.
+ val mockCallback = mock(NetworkCallback::class.java)
+
+ if (!Modifier.isFinal(method.modifiers) ||
+ // The mock class will be NetworkCallback (not a subclass) if using mockito-inline,
+ // which mocks final methods too
+ mockCallback.javaClass == NetworkCallback::class.java) {
+ doCallRealMethod().`when`(mockCallback).let { mockObj ->
+ val anyArgs = method.parameterTypes.map { any(it) }
+ method.invoke(mockObj, *anyArgs.toTypedArray())
+ }
+ }
+
+ // Invoke the target method with mock parameters
+ val mockParameters = method.parameterTypes.map { getMockFor(method, it) }
+ method.invoke(mockCallback, *mockParameters.toTypedArray())
+
+ // Aggregate callees
+ val mockingDetails = mockingDetails(mockCallback)
+ return mockingDetails.invocations.mapNotNull { inv ->
+ if (inv.method == method) {
+ null
+ } else {
+ inv.method.getAnnotation(NetworkCallback.FilteredCallback::class.java)?.methodId
+ }
+ }.toSet()
+ }
+
+ private fun getMockFor(method: Method, c: Class<*>): Any {
+ if (!c.isPrimitive && !Modifier.isFinal(c.modifiers)) {
+ return mock(c)
+ }
+ return when (c) {
+ NetworkCapabilities::class.java -> NetworkCapabilities()
+ LinkProperties::class.java -> LinkProperties()
+ LocalNetworkInfo::class.java -> LocalNetworkInfo(null)
+ Boolean::class.java -> false
+ Int::class.java -> 0
+ else -> fail("No mock set for parameter type $c used in $method")
+ }
+ }
+}
diff --git a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
index aa28e5a..10ba6a4 100644
--- a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
+++ b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
@@ -163,7 +163,8 @@
val sentQueryCount = 150
val metrics = NetworkNsdReportedMetrics(clientId, deps)
metrics.reportServiceDiscoveryStop(true /* isLegacy */, transactionId, durationMs,
- foundCallbackCount, lostCallbackCount, servicesCount, sentQueryCount)
+ foundCallbackCount, lostCallbackCount, servicesCount, sentQueryCount,
+ true /* isServiceFromCache */)
val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
verify(deps).statsWrite(eventCaptor.capture())
@@ -179,6 +180,7 @@
assertEquals(servicesCount, it.foundServiceCount)
assertEquals(durationMs, it.eventDurationMillisec)
assertEquals(sentQueryCount, it.sentQueryCount)
+ assertTrue(it.isKnownService)
}
}
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index cbc060a..859c54a 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -87,7 +87,9 @@
import android.net.InetAddresses;
import android.net.UidOwnerValue;
import android.os.Build;
+import android.os.Process;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.system.ErrnoException;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
@@ -1249,6 +1251,32 @@
);
}
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testIsUidNetworkingBlockedForCoreUids() throws Exception {
+ final long allowlistMatch = BACKGROUND_MATCH; // Enable any allowlist match.
+ mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(allowlistMatch));
+
+ // Verify that a normal uid that is not on this chain is indeed blocked.
+ assertTrue(BpfNetMapsUtils.isUidNetworkingBlocked(TEST_UID, false, mConfigurationMap,
+ mUidOwnerMap, mDataSaverEnabledMap));
+
+ final int[] coreAids = new int[] {
+ Process.ROOT_UID,
+ Process.SYSTEM_UID,
+ Process.FIRST_APPLICATION_UID - 10,
+ Process.FIRST_APPLICATION_UID - 1,
+ };
+ // Core appIds are not on the chain but should still be allowed on any user.
+ for (int userId = 0; userId < 20; userId++) {
+ for (final int aid : coreAids) {
+ final int uid = UserHandle.getUid(userId, aid);
+ assertFalse(BpfNetMapsUtils.isUidNetworkingBlocked(uid, false, mConfigurationMap,
+ mUidOwnerMap, mDataSaverEnabledMap));
+ }
+ }
+ }
+
private void doTestIsUidRestrictedOnMeteredNetworks(
final long enabledMatches,
final long uidRules,
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index b6cb09b..be7f2a3 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -53,6 +53,7 @@
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.EXTRA_DEVICE_TYPE;
@@ -163,7 +164,6 @@
import static android.telephony.DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
import static com.android.server.ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK;
-import static com.android.server.ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION;
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
import static com.android.server.ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS;
import static com.android.server.ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION;
@@ -178,6 +178,7 @@
import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister;
import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister;
import static com.android.server.connectivity.ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN;
+import static com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS;
import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.ConcurrentUtils.await;
@@ -1746,7 +1747,15 @@
private void setBlockedReasonChanged(int blockedReasons) {
mBlockedReasons = blockedReasons;
- mPolicyCallback.onUidBlockedReasonChanged(Process.myUid(), blockedReasons);
+ if (mDeps.isAtLeastV()) {
+ visibleOnHandlerThread(mCsHandlerThread.getThreadHandler(),
+ () -> mService.handleBlockedReasonsChanged(
+ List.of(new Pair<>(Process.myUid(), blockedReasons))
+
+ ));
+ } else {
+ mPolicyCallback.onUidBlockedReasonChanged(Process.myUid(), blockedReasons);
+ }
}
private Nat464Xlat getNat464Xlat(NetworkAgentWrapper mna) {
@@ -1927,11 +1936,16 @@
mService.mLingerDelayMs = TEST_LINGER_DELAY_MS;
mService.mNascentDelayMs = TEST_NASCENT_DELAY_MS;
- final ArgumentCaptor<NetworkPolicyCallback> policyCallbackCaptor =
- ArgumentCaptor.forClass(NetworkPolicyCallback.class);
- verify(mNetworkPolicyManager).registerNetworkPolicyCallback(any(),
- policyCallbackCaptor.capture());
- mPolicyCallback = policyCallbackCaptor.getValue();
+ if (mDeps.isAtLeastV()) {
+ verify(mNetworkPolicyManager, never()).registerNetworkPolicyCallback(any(), any());
+ mPolicyCallback = null;
+ } else {
+ final ArgumentCaptor<NetworkPolicyCallback> policyCallbackCaptor =
+ ArgumentCaptor.forClass(NetworkPolicyCallback.class);
+ verify(mNetworkPolicyManager).registerNetworkPolicyCallback(any(),
+ policyCallbackCaptor.capture());
+ mPolicyCallback = policyCallbackCaptor.getValue();
+ }
// Create local CM before sending system ready so that we can answer
// getSystemService() correctly.
@@ -2164,13 +2178,10 @@
switch (name) {
case ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER:
case ConnectivityFlags.CARRIER_SERVICE_CHANGED_USE_CALLBACK:
- return true;
case ConnectivityFlags.REQUEST_RESTRICTED_WIFI:
- return true;
+ case ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS:
case KEY_DESTROY_FROZEN_SOCKETS_VERSION:
return true;
- case DELAY_DESTROY_FROZEN_SOCKETS_VERSION:
- return true;
default:
return super.isFeatureEnabled(context, name);
}
@@ -2187,6 +2198,8 @@
return true;
case BACKGROUND_FIREWALL_CHAIN:
return true;
+ case DELAY_DESTROY_SOCKETS:
+ return true;
default:
return super.isFeatureNotChickenedOut(context, name);
}
@@ -2858,7 +2871,7 @@
};
final NetworkRequest request = mService.listenForNetwork(caps, messenger, binder,
NetworkCallback.FLAG_NONE, mContext.getOpPackageName(),
- mContext.getAttributionTag());
+ mContext.getAttributionTag(), ~0 /* declaredMethodsFlag */);
mService.releaseNetworkRequest(request);
deathRecipient.get().binderDied();
// Wait for the release message to be processed.
@@ -5394,7 +5407,7 @@
mService.requestNetwork(Process.INVALID_UID, networkCapabilities,
NetworkRequest.Type.REQUEST.ordinal(), null, 0, null,
ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE,
- mContext.getPackageName(), getAttributionTag());
+ mContext.getPackageName(), getAttributionTag(), ~0 /* declaredMethodsFlag */);
});
final NetworkRequest.Builder builder =
@@ -7527,13 +7540,13 @@
@Test
public void testNetworkCallbackMaximum() throws Exception {
final int MAX_REQUESTS = 100;
- final int CALLBACKS = 87;
+ final int CALLBACKS = 88;
final int DIFF_INTENTS = 10;
final int SAME_INTENTS = 10;
final int SYSTEM_ONLY_MAX_REQUESTS = 250;
- // Assert 1 (Default request filed before testing) + CALLBACKS + DIFF_INTENTS +
- // 1 (same intent) = MAX_REQUESTS - 1, since the capacity is MAX_REQUEST - 1.
- assertEquals(MAX_REQUESTS - 1, 1 + CALLBACKS + DIFF_INTENTS + 1);
+ // CALLBACKS + DIFF_INTENTS + 1 (same intent)
+ // = MAX_REQUESTS - 1, since the capacity is MAX_REQUEST - 1.
+ assertEquals(MAX_REQUESTS - 1, CALLBACKS + DIFF_INTENTS + 1);
NetworkRequest networkRequest = new NetworkRequest.Builder().build();
ArrayList<Object> registered = new ArrayList<>();
@@ -9867,6 +9880,28 @@
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellAgent);
+ // Remove PERMISSION_INTERNET and disable NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION
+ doReturn(INetd.PERMISSION_NONE).when(mBpfNetMaps).getNetPermForUid(Process.myUid());
+ mDeps.setChangeIdEnabled(false,
+ NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION, Process.myUid());
+
+ setBlockedReasonChanged(BLOCKED_REASON_DOZE);
+ if (mDeps.isAtLeastV()) {
+ // On V+, network access from app that does not have INTERNET permission is considered
+ // not blocked if NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION is disabled.
+ // So blocked status does not change from BLOCKED_REASON_NONE
+ cellNetworkCallback.assertNoCallback();
+ detailedCallback.assertNoCallback();
+ } else {
+ // On U-, onBlockedStatusChanged callback is called with blocked reasons CS receives
+ // from NPMS callback regardless of permission app has.
+ // Note that this cannot actually happen because on U-, NPMS will never notify any
+ // blocked reasons for apps that don't have the INTERNET permission.
+ cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> cb.getBlocked());
+ detailedCallback.expect(BLOCKED_STATUS_INT, mCellAgent,
+ cb -> cb.getReason() == BLOCKED_REASON_DOZE);
+ }
+
mCm.unregisterNetworkCallback(cellNetworkCallback);
}
@@ -13620,7 +13655,8 @@
IllegalArgumentException.class,
() -> mService.requestNetwork(Process.INVALID_UID, nc, reqTypeInt, null, 0,
null, ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE,
- mContext.getPackageName(), getAttributionTag())
+ mContext.getPackageName(), getAttributionTag(),
+ ~0 /* declaredMethodsFlag */)
);
}
}
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index aece3f7..979e0a1 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -1196,7 +1196,7 @@
Instant.MAX /* expirationTime */);
// Verify onServiceNameDiscovered callback
- listener.onServiceNameDiscovered(foundInfo, false /* isServiceFromCache */);
+ listener.onServiceNameDiscovered(foundInfo, true /* isServiceFromCache */);
verify(discListener, timeout(TIMEOUT_MS)).onServiceFound(argThat(info ->
info.getServiceName().equals(SERVICE_NAME)
// Service type in discovery callbacks has a dot at the end
@@ -1232,7 +1232,7 @@
verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
verify(mMetrics).reportServiceDiscoveryStop(false /* isLegacy */, discId,
10L /* durationMs */, 1 /* foundCallbackCount */, 1 /* lostCallbackCount */,
- 1 /* servicesCount */, 3 /* sentQueryCount */);
+ 1 /* servicesCount */, 3 /* sentQueryCount */, true /* isServiceFromCache */);
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
index 44512bb..ea3d2dd 100644
--- a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
@@ -29,8 +29,6 @@
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
-import static com.android.testutils.MiscAsserts.assertContainsExactly;
-import static com.android.testutils.MiscAsserts.assertContainsStringsExactly;
import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
import static org.junit.Assert.assertEquals;
@@ -38,12 +36,12 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.net.ConnectivitySettingsManager;
@@ -74,7 +72,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -122,29 +119,6 @@
assertFieldCountEquals(3, ResolverOptionsParcel.class);
}
- private void assertResolverParamsEquals(@NonNull ResolverParamsParcel actual,
- @NonNull ResolverParamsParcel expected) {
- assertEquals(actual.netId, expected.netId);
- assertEquals(actual.sampleValiditySeconds, expected.sampleValiditySeconds);
- assertEquals(actual.successThreshold, expected.successThreshold);
- assertEquals(actual.minSamples, expected.minSamples);
- assertEquals(actual.maxSamples, expected.maxSamples);
- assertEquals(actual.baseTimeoutMsec, expected.baseTimeoutMsec);
- assertEquals(actual.retryCount, expected.retryCount);
- assertContainsStringsExactly(actual.servers, expected.servers);
- assertContainsStringsExactly(actual.domains, expected.domains);
- assertEquals(actual.tlsName, expected.tlsName);
- assertContainsStringsExactly(actual.tlsServers, expected.tlsServers);
- assertContainsStringsExactly(actual.tlsFingerprints, expected.tlsFingerprints);
- assertEquals(actual.caCertificate, expected.caCertificate);
- assertEquals(actual.tlsConnectTimeoutMs, expected.tlsConnectTimeoutMs);
- assertResolverOptionsEquals(actual.resolverOptions, expected.resolverOptions);
- assertContainsExactly(actual.transportTypes, expected.transportTypes);
- assertEquals(actual.meteredNetwork, expected.meteredNetwork);
- assertEquals(actual.dohParams, expected.dohParams);
- assertFieldCountEquals(18, ResolverParamsParcel.class);
- }
-
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -365,11 +339,6 @@
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
mDnsManager.flushVmDnsCache();
- final ArgumentCaptor<ResolverParamsParcel> resolverParamsParcelCaptor =
- ArgumentCaptor.forClass(ResolverParamsParcel.class);
- verify(mMockDnsResolver, times(1)).setResolverConfiguration(
- resolverParamsParcelCaptor.capture());
- final ResolverParamsParcel actualParams = resolverParamsParcelCaptor.getValue();
final ResolverParamsParcel expectedParams = new ResolverParamsParcel();
expectedParams.netId = TEST_NETID;
expectedParams.sampleValiditySeconds = TEST_DEFAULT_SAMPLE_VALIDITY_SECONDS;
@@ -384,7 +353,8 @@
expectedParams.resolverOptions = null;
expectedParams.meteredNetwork = true;
expectedParams.dohParams = null;
- assertResolverParamsEquals(actualParams, expectedParams);
+ expectedParams.interfaceNames = new String[]{TEST_IFACENAME};
+ verify(mMockDnsResolver, times(1)).setResolverConfiguration(eq(expectedParams));
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 7121ed4..727db58 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -113,6 +113,7 @@
private static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
private static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
private static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
+ private static final NetworkCapabilities BT_CAPABILITIES = new NetworkCapabilities();
static {
CELL_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
CELL_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
@@ -128,6 +129,9 @@
VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
VPN_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
VPN_CAPABILITIES.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+
+ BT_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH);
+ BT_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
}
/**
@@ -159,7 +163,9 @@
@Mock NetworkAgentInfo mWifiNai;
@Mock NetworkAgentInfo mCellNai;
@Mock NetworkAgentInfo mVpnNai;
+ @Mock NetworkAgentInfo mBluetoothNai;
@Mock NetworkInfo mNetworkInfo;
+ @Mock NetworkInfo mEmptyNetworkInfo;
ArgumentCaptor<Notification> mCaptor;
NetworkNotificationManager mManager;
@@ -174,6 +180,8 @@
mCellNai.networkInfo = mNetworkInfo;
mVpnNai.networkCapabilities = VPN_CAPABILITIES;
mVpnNai.networkInfo = mNetworkInfo;
+ mBluetoothNai.networkCapabilities = BT_CAPABILITIES;
+ mBluetoothNai.networkInfo = mEmptyNetworkInfo;
mDisplayMetrics.density = 2.275f;
doReturn(true).when(mVpnNai).isVPN();
doReturn(mResources).when(mCtx).getResources();
@@ -542,10 +550,11 @@
R.string.wifi_no_internet_detailed);
}
- private void runTelephonySignInNotificationTest(String testTitle, String testContents) {
+ private void runSignInNotificationTest(NetworkAgentInfo nai, String testTitle,
+ String testContents) {
final int id = 101;
final String tag = NetworkNotificationManager.tagFor(id);
- mManager.showNotification(id, SIGN_IN, mCellNai, null, null, false);
+ mManager.showNotification(id, SIGN_IN, nai, null, null, false);
final ArgumentCaptor<Notification> noteCaptor = ArgumentCaptor.forClass(Notification.class);
verify(mNotificationManager).notify(eq(tag), eq(SIGN_IN.eventId), noteCaptor.capture());
@@ -565,7 +574,7 @@
doReturn(testContents).when(mResources).getString(
R.string.mobile_network_available_no_internet_detailed, TEST_OPERATOR_NAME);
- runTelephonySignInNotificationTest(testTitle, testContents);
+ runSignInNotificationTest(mCellNai, testTitle, testContents);
}
@Test
@@ -579,6 +588,21 @@
doReturn(testContents).when(mResources).getString(
R.string.mobile_network_available_no_internet_detailed_unknown_carrier);
- runTelephonySignInNotificationTest(testTitle, testContents);
+ runSignInNotificationTest(mCellNai, testTitle, testContents);
+ }
+
+ @Test
+ public void testBluetoothSignInNotification_EmptyNotificationContents() {
+ final String testTitle = "Test title";
+ final String testContents = "Test contents";
+ doReturn(testTitle).when(mResources).getString(
+ R.string.network_available_sign_in, 0);
+ doReturn(testContents).when(mResources).getString(
+ eq(R.string.network_available_sign_in_detailed), any());
+
+ runSignInNotificationTest(mBluetoothNai, testTitle, testContents);
+ // The details should be queried with an empty string argument. In practice the notification
+ // contents may just be an empty string, since the default translation just outputs the arg.
+ verify(mResources).getString(eq(R.string.network_available_sign_in_detailed), eq(""));
}
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
new file mode 100644
index 0000000..3ad8de8
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
@@ -0,0 +1,422 @@
+/*
+ * 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.server
+
+import android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER
+import android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED
+import android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND
+import android.net.ConnectivityManager.BLOCKED_REASON_DOZE
+import android.net.ConnectivityManager.BLOCKED_REASON_NETWORK_RESTRICTED
+import android.net.ConnectivityManager.BLOCKED_REASON_NONE
+import android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND
+import android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE
+import android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER
+import android.net.ConnectivityManager.FIREWALL_RULE_ALLOW
+import android.net.ConnectivityManager.FIREWALL_RULE_DENY
+import android.net.ConnectivitySettingsManager
+import android.net.INetd.PERMISSION_NONE
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.connectivity.ConnectivityCompatChanges.NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION
+import android.os.Build
+import android.os.Process
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatusInt
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mockito.doReturn
+
+private fun cellNc() = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build()
+private fun cellRequest() = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .build()
+private fun wifiNc() = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .build()
+private fun wifiRequest() = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build()
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class CSBlockedReasonsTest : CSTest() {
+
+ inner class DetailedBlockedStatusCallback : TestableNetworkCallback() {
+ override fun onBlockedStatusChanged(network: Network, blockedReasons: Int) {
+ history.add(BlockedStatusInt(network, blockedReasons))
+ }
+
+ fun expectBlockedStatusChanged(network: Network, blockedReasons: Int) {
+ expect<BlockedStatusInt>(network) { it.reason == blockedReasons }
+ }
+ }
+
+ @Test
+ fun testBlockedReasons_onAvailable() {
+ doReturn(BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_DATA_SAVER)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+
+ val cellCb = DetailedBlockedStatusCallback()
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(cellRequest(), cellCb)
+ cm.requestNetwork(wifiRequest(), wifiCb)
+
+ cellCb.expectAvailableCallbacks(
+ cellAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_DATA_SAVER
+ )
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_DOZE
+ )
+
+ cellAgent.disconnect()
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(cellCb)
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_dataSaverChanged() {
+ doReturn(BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ doReturn(true).`when`(netd).bandwidthEnableDataSaver(anyBoolean())
+
+ val cellCb = DetailedBlockedStatusCallback()
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(cellRequest(), cellCb)
+ cm.requestNetwork(wifiRequest(), wifiCb)
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ cellCb.expectAvailableCallbacks(
+ cellAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER
+ )
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_APP_BACKGROUND
+ )
+
+ // Disable data saver
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setDataSaverEnabled(false)
+ cellCb.expectBlockedStatusChanged(cellAgent.network, BLOCKED_REASON_APP_BACKGROUND)
+
+ // waitForIdle since stubbing bpfNetMaps while CS handler thread calls
+ // bpfNetMaps.getNetPermForUid throws exception.
+ // The expectBlockedStatusChanged just above guarantees that the onBlockedStatusChanged
+ // method on this callback was called, but it does not guarantee that ConnectivityService
+ // has finished processing all onBlockedStatusChanged callbacks for all requests.
+ waitForIdle()
+ // Enable data saver
+ doReturn(BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setDataSaverEnabled(true)
+ cellCb.expectBlockedStatusChanged(
+ cellAgent.network,
+ BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER
+ )
+ // BlockedStatus does not change for the non-metered network
+ wifiCb.assertNoCallback()
+
+ cellAgent.disconnect()
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(cellCb)
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_setUidFirewallRule() {
+ doReturn(BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val cellCb = DetailedBlockedStatusCallback()
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(cellRequest(), cellCb)
+ cm.requestNetwork(wifiRequest(), wifiCb)
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ cellCb.expectAvailableCallbacks(
+ cellAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED
+ )
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_DOZE
+ )
+
+ // waitForIdle since stubbing bpfNetMaps while CS handler thread calls
+ // bpfNetMaps.getNetPermForUid throws exception.
+ // The expectBlockedStatusChanged just above guarantees that the onBlockedStatusChanged
+ // method on this callback was called, but it does not guarantee that ConnectivityService
+ // has finished processing all onBlockedStatusChanged callbacks for all requests.
+ waitForIdle()
+ // Set RULE_ALLOW on metered deny chain
+ doReturn(BLOCKED_REASON_DOZE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_METERED_DENY_USER,
+ Process.myUid(),
+ FIREWALL_RULE_ALLOW
+ )
+ cellCb.expectBlockedStatusChanged(
+ cellAgent.network,
+ BLOCKED_REASON_DOZE
+ )
+ // BlockedStatus does not change for the non-metered network
+ wifiCb.assertNoCallback()
+
+ // Set RULE_DENY on metered deny chain
+ doReturn(BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_METERED_DENY_USER,
+ Process.myUid(),
+ FIREWALL_RULE_DENY
+ )
+ cellCb.expectBlockedStatusChanged(
+ cellAgent.network,
+ BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED
+ )
+ // BlockedStatus does not change for the non-metered network
+ wifiCb.assertNoCallback()
+
+ cellAgent.disconnect()
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(cellCb)
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_setFirewallChainEnabled() {
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(wifiRequest(), wifiCb)
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_NONE
+ )
+
+ // Enable dozable firewall chain
+ doReturn(BLOCKED_REASON_DOZE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_DOZABLE, true)
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_DOZE
+ )
+
+ // Disable dozable firewall chain
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_DOZABLE, false)
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_NONE
+ )
+
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_replaceFirewallChain() {
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(wifiRequest(), wifiCb)
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_APP_BACKGROUND
+ )
+
+ // Put uid on background firewall chain
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, intArrayOf(Process.myUid()))
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_NONE
+ )
+
+ // Remove uid from background firewall chain
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, intArrayOf())
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_APP_BACKGROUND
+ )
+
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_perAppDefaultNetwork() {
+ doReturn(BLOCKED_METERED_REASON_USER_RESTRICTED)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val cellCb = DetailedBlockedStatusCallback()
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(cellRequest(), cellCb)
+ cm.requestNetwork(wifiRequest(), wifiCb)
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+
+ val cb = DetailedBlockedStatusCallback()
+ cm.registerDefaultNetworkCallback(cb)
+ cb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_NONE
+ )
+
+ // CS must send correct blocked reasons after per app default network change
+ ConnectivitySettingsManager.setMobileDataPreferredUids(context, setOf(Process.myUid()))
+ service.updateMobileDataPreferredUids()
+ cb.expectAvailableCallbacks(
+ cellAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_METERED_REASON_USER_RESTRICTED
+ )
+
+ // Remove per app default network request
+ ConnectivitySettingsManager.setMobileDataPreferredUids(context, setOf())
+ service.updateMobileDataPreferredUids()
+ cb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_NONE
+ )
+
+ cellAgent.disconnect()
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(cellCb)
+ cm.unregisterNetworkCallback(wifiCb)
+ cm.unregisterNetworkCallback(cb)
+ }
+
+ private fun doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission: Boolean) {
+ doReturn(PERMISSION_NONE).`when`(bpfNetMaps).getNetPermForUid(Process.myUid())
+
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(wifiRequest(), wifiCb)
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ val expectedBlockedReason = if (blockedByNoInternetPermission) {
+ BLOCKED_REASON_NETWORK_RESTRICTED
+ } else {
+ BLOCKED_REASON_NONE
+ }
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = expectedBlockedReason
+ )
+
+ // Enable background firewall chain
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true)
+ if (blockedByNoInternetPermission) {
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_NETWORK_RESTRICTED or BLOCKED_REASON_APP_BACKGROUND
+ )
+ }
+ // waitForIdle since stubbing bpfNetMaps while CS handler thread calls
+ // bpfNetMaps.getNetPermForUid throws exception.
+ // ConnectivityService might haven't finished checking blocked status for all requests.
+ waitForIdle()
+
+ // Disable background firewall chain
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, false)
+ if (blockedByNoInternetPermission) {
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_NETWORK_RESTRICTED
+ )
+ } else {
+ // No callback is expected since blocked reasons does not change from
+ // BLOCKED_REASON_NONE.
+ wifiCb.assertNoCallback()
+ }
+ }
+
+ @Test
+ fun testBlockedReasonsNoInternetPermission_changeDisabled() {
+ deps.setChangeIdEnabled(false, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
+ doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission = false)
+ }
+
+ @Test
+ fun testBlockedReasonsNoInternetPermission_changeEnabled() {
+ deps.setChangeIdEnabled(true, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
+ doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission = true)
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
new file mode 100644
index 0000000..cf990b1
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.server.connectivityservice
+
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.CALLBACK_CAP_CHANGED
+import android.net.ConnectivityManager.CALLBACK_IP_CHANGED
+import android.net.ConnectivityManager.CALLBACK_LOST
+import android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_ALL
+import android.net.LinkAddress
+import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
+import android.net.NetworkRequest
+import android.os.Build
+import com.android.net.module.util.BitUtils.packBits
+import com.android.server.CSTest
+import com.android.server.ConnectivityService
+import com.android.server.defaultLp
+import com.android.server.defaultNc
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.tryTest
+import java.util.concurrent.atomic.AtomicInteger
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.spy
+
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+class CSDeclaredMethodsForCallbacksTest : CSTest() {
+ private val mockedCallbackFlags = AtomicInteger(DECLARED_METHODS_ALL)
+ private lateinit var wrappedService: ConnectivityService
+
+ private val instrumentedCm by lazy { ConnectivityManager(context, wrappedService) }
+
+ @Before
+ fun setUpWrappedService() {
+ // Mock the callback flags set by ConnectivityManager when calling ConnectivityService, to
+ // simulate methods not being overridden
+ wrappedService = spy(service)
+ doAnswer { inv ->
+ service.requestNetwork(
+ inv.getArgument(0),
+ inv.getArgument(1),
+ inv.getArgument(2),
+ inv.getArgument(3),
+ inv.getArgument(4),
+ inv.getArgument(5),
+ inv.getArgument(6),
+ inv.getArgument(7),
+ inv.getArgument(8),
+ inv.getArgument(9),
+ mockedCallbackFlags.get())
+ }.`when`(wrappedService).requestNetwork(
+ anyInt(),
+ any(),
+ anyInt(),
+ any(),
+ anyInt(),
+ any(),
+ anyInt(),
+ anyInt(),
+ any(),
+ any(),
+ anyInt()
+ )
+ doAnswer { inv ->
+ service.listenForNetwork(
+ inv.getArgument(0),
+ inv.getArgument(1),
+ inv.getArgument(2),
+ inv.getArgument(3),
+ inv.getArgument(4),
+ inv.getArgument(5),
+ mockedCallbackFlags.get()
+ )
+ }.`when`(wrappedService)
+ .listenForNetwork(any(), any(), any(), anyInt(), any(), any(), anyInt())
+ }
+
+ @Test
+ fun testCallbacksAreFiltered() {
+ val requestCb = TestableNetworkCallback()
+ val listenCb = TestableNetworkCallback()
+ mockedCallbackFlags.withFlags(CALLBACK_IP_CHANGED, CALLBACK_LOST) {
+ instrumentedCm.requestNetwork(NetworkRequest.Builder().build(), requestCb)
+ }
+ mockedCallbackFlags.withFlags(CALLBACK_CAP_CHANGED) {
+ instrumentedCm.registerNetworkCallback(NetworkRequest.Builder().build(), listenCb)
+ }
+
+ with(Agent()) {
+ connect()
+ sendLinkProperties(defaultLp().apply {
+ addLinkAddress(LinkAddress("fe80:db8::123/64"))
+ })
+ sendNetworkCapabilities(defaultNc().apply {
+ addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ })
+ disconnect()
+ }
+ waitForIdle()
+
+ // Only callbacks for the corresponding flags are called
+ requestCb.expect<CallbackEntry.LinkPropertiesChanged>()
+ requestCb.expect<CallbackEntry.Lost>()
+ requestCb.assertNoCallback(timeoutMs = 0L)
+
+ listenCb.expect<CallbackEntry.CapabilitiesChanged>()
+ listenCb.assertNoCallback(timeoutMs = 0L)
+ }
+}
+
+private fun AtomicInteger.withFlags(vararg flags: Int, action: () -> Unit) {
+ tryTest {
+ set(packBits(flags).toInt())
+ action()
+ } cleanup {
+ set(DECLARED_METHODS_ALL)
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt
new file mode 100644
index 0000000..bc5be78
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt
@@ -0,0 +1,338 @@
+/*
+ * 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.server
+
+import android.app.ActivityManager.UidFrozenStateChangedCallback
+import android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN
+import android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN
+import android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND
+import android.net.ConnectivityManager.BLOCKED_REASON_NONE
+import android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND
+import android.net.ConnectivityManager.FIREWALL_RULE_ALLOW
+import android.net.ConnectivityManager.FIREWALL_RULE_DENY
+import android.net.LinkProperties
+import android.net.NetworkCapabilities
+import android.os.Build
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener
+import com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+private const val TIMESTAMP = 1234L
+private const val TEST_UID = 1234
+private const val TEST_UID2 = 5678
+private const val TEST_CELL_IFACE = "test_rmnet"
+
+private fun cellNc() = NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build()
+
+private fun cellLp() = LinkProperties().also{
+ it.interfaceName = TEST_CELL_IFACE
+}
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class CSDestroySocketTest : CSTest() {
+ private fun getRegisteredNetdUnsolicitedEventListener(): BaseNetdUnsolicitedEventListener {
+ val captor = ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener::class.java)
+ verify(netd).registerUnsolicitedEventListener(captor.capture())
+ return captor.value
+ }
+
+ private fun getUidFrozenStateChangedCallback(): UidFrozenStateChangedCallback {
+ val captor = ArgumentCaptor.forClass(UidFrozenStateChangedCallback::class.java)
+ verify(activityManager).registerUidFrozenStateChangedCallback(any(), captor.capture())
+ return captor.value
+ }
+
+ private fun doTestBackgroundRestrictionDestroySockets(
+ restrictionWithIdleNetwork: Boolean,
+ expectDelay: Boolean
+ ) {
+ val netdEventListener = getRegisteredNetdUnsolicitedEventListener()
+ val inOrder = inOrder(destroySocketsWrapper)
+
+ val cellAgent = Agent(nc = cellNc(), lp = cellLp())
+ cellAgent.connect()
+ if (restrictionWithIdleNetwork) {
+ // Make cell default network idle
+ netdEventListener.onInterfaceClassActivityChanged(
+ false, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+ }
+
+ // Set deny rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID,
+ FIREWALL_RULE_DENY
+ )
+ waitForIdle()
+ if (expectDelay) {
+ inOrder.verify(destroySocketsWrapper, never())
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ } else {
+ inOrder.verify(destroySocketsWrapper)
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ }
+
+ netdEventListener.onInterfaceClassActivityChanged(
+ true, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+ waitForIdle()
+ if (expectDelay) {
+ inOrder.verify(destroySocketsWrapper)
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ } else {
+ inOrder.verify(destroySocketsWrapper, never())
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ }
+
+ cellAgent.disconnect()
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(DELAY_DESTROY_SOCKETS, true)])
+ fun testBackgroundAppDestroySockets() {
+ doTestBackgroundRestrictionDestroySockets(
+ restrictionWithIdleNetwork = true,
+ expectDelay = true
+ )
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(DELAY_DESTROY_SOCKETS, true)])
+ fun testBackgroundAppDestroySockets_activeNetwork() {
+ doTestBackgroundRestrictionDestroySockets(
+ restrictionWithIdleNetwork = false,
+ expectDelay = false
+ )
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(DELAY_DESTROY_SOCKETS, false)])
+ fun testBackgroundAppDestroySockets_featureIsDisabled() {
+ doTestBackgroundRestrictionDestroySockets(
+ restrictionWithIdleNetwork = true,
+ expectDelay = false
+ )
+ }
+
+ @Test
+ fun testReplaceFirewallChain() {
+ val netdEventListener = getRegisteredNetdUnsolicitedEventListener()
+ val inOrder = inOrder(destroySocketsWrapper)
+
+ val cellAgent = Agent(nc = cellNc(), lp = cellLp())
+ cellAgent.connect()
+ // Make cell default network idle
+ netdEventListener.onInterfaceClassActivityChanged(
+ false, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+
+ // Set allow rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID,
+ FIREWALL_RULE_ALLOW
+ )
+ // Set deny rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID2)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID2,
+ FIREWALL_RULE_DENY
+ )
+
+ // Put only TEST_UID2 on background chain (deny TEST_UID and allow TEST_UID2)
+ doReturn(setOf(TEST_UID))
+ .`when`(bpfNetMaps).getUidsWithAllowRuleOnAllowListChain(FIREWALL_CHAIN_BACKGROUND)
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID2)
+ cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, intArrayOf(TEST_UID2))
+ waitForIdle()
+ inOrder.verify(destroySocketsWrapper, never())
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+
+ netdEventListener.onInterfaceClassActivityChanged(
+ true, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+ waitForIdle()
+ inOrder.verify(destroySocketsWrapper)
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+
+ cellAgent.disconnect()
+ }
+
+ private fun doTestDestroySockets(
+ isFrozen: Boolean,
+ denyOnBackgroundChain: Boolean,
+ enableBackgroundChain: Boolean,
+ expectDestroySockets: Boolean
+ ) {
+ val netdEventListener = getRegisteredNetdUnsolicitedEventListener()
+ val frozenStateCallback = getUidFrozenStateChangedCallback()
+
+ // Make cell default network idle
+ val cellAgent = Agent(nc = cellNc(), lp = cellLp())
+ cellAgent.connect()
+ netdEventListener.onInterfaceClassActivityChanged(
+ false, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+
+ // Set deny rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID,
+ FIREWALL_RULE_DENY
+ )
+
+ // Freeze TEST_UID
+ frozenStateCallback.onUidFrozenStateChanged(
+ intArrayOf(TEST_UID),
+ intArrayOf(UID_FROZEN_STATE_FROZEN)
+ )
+
+ if (!isFrozen) {
+ // Unfreeze TEST_UID
+ frozenStateCallback.onUidFrozenStateChanged(
+ intArrayOf(TEST_UID),
+ intArrayOf(UID_FROZEN_STATE_UNFROZEN)
+ )
+ }
+ if (!enableBackgroundChain) {
+ // Disable background chain
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, false)
+ }
+ if (!denyOnBackgroundChain) {
+ // Set allow rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID,
+ FIREWALL_RULE_ALLOW
+ )
+ }
+ verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+
+ // Make cell network active
+ netdEventListener.onInterfaceClassActivityChanged(
+ true, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+ waitForIdle()
+
+ if (expectDestroySockets) {
+ verify(destroySocketsWrapper).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ } else {
+ verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ }
+ }
+
+ @Test
+ fun testDestroySockets_backgroundDeny_frozen() {
+ doTestDestroySockets(
+ isFrozen = true,
+ denyOnBackgroundChain = true,
+ enableBackgroundChain = true,
+ expectDestroySockets = true
+ )
+ }
+
+ @Test
+ fun testDestroySockets_backgroundDeny_nonFrozen() {
+ doTestDestroySockets(
+ isFrozen = false,
+ denyOnBackgroundChain = true,
+ enableBackgroundChain = true,
+ expectDestroySockets = true
+ )
+ }
+
+ @Test
+ fun testDestroySockets_backgroundAllow_frozen() {
+ doTestDestroySockets(
+ isFrozen = true,
+ denyOnBackgroundChain = false,
+ enableBackgroundChain = true,
+ expectDestroySockets = true
+ )
+ }
+
+ @Test
+ fun testDestroySockets_backgroundAllow_nonFrozen() {
+ // If the app is neither frozen nor under background restriction, sockets are not
+ // destroyed
+ doTestDestroySockets(
+ isFrozen = false,
+ denyOnBackgroundChain = false,
+ enableBackgroundChain = true,
+ expectDestroySockets = false
+ )
+ }
+
+ @Test
+ fun testDestroySockets_backgroundChainDisabled_nonFrozen() {
+ // If the app is neither frozen nor under background restriction, sockets are not
+ // destroyed
+ doTestDestroySockets(
+ isFrozen = false,
+ denyOnBackgroundChain = true,
+ enableBackgroundChain = false,
+ expectDestroySockets = false
+ )
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
index bb7fb51..93f6e81 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
@@ -37,6 +37,7 @@
import com.android.testutils.TestableNetworkCallback
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.InOrder
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.timeout
@@ -96,6 +97,11 @@
private val LOCAL_IPV6_ADDRRESS = InetAddresses.parseNumericAddress("fe80::1234")
private val LOCAL_IPV6_LINK_ADDRRESS = LinkAddress(LOCAL_IPV6_ADDRRESS, 64)
+ fun verifyNoMoreIngressDiscardRuleChange(inorder: InOrder) {
+ inorder.verify(bpfNetMaps, never()).setIngressDiscardRule(any(), any())
+ inorder.verify(bpfNetMaps, never()).removeIngressDiscardRule(any())
+ }
+
@Test
fun testVpnIngressDiscardRule_UpdateVpnAddress() {
// non-VPN network whose address will be not duplicated with VPN address
@@ -148,7 +154,7 @@
// IngressDiscardRule is added to the VPN address
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
// The VPN interface name is changed
val newlp = lp(VPN_IFNAME2, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
@@ -157,7 +163,7 @@
// IngressDiscardRule is updated with the new interface name
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME2)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
agent.disconnect()
inorder.verify(bpfNetMaps, timeout(TIMEOUT_MS)).removeIngressDiscardRule(IPV6_ADDRESS)
@@ -206,10 +212,10 @@
// IngressDiscardRule for IPV6_ADDRESS2 is removed but IngressDiscardRule for
// IPV6_LINK_ADDRESS is not added since Wi-Fi also uses IPV6_LINK_ADDRESS
inorder.verify(bpfNetMaps).removeIngressDiscardRule(IPV6_ADDRESS2)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
vpnAgent.disconnect()
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
cm.unregisterNetworkCallback(cb)
}
@@ -225,7 +231,7 @@
// IngressDiscardRule is added to the VPN address
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
val nr = nr(TRANSPORT_WIFI)
val cb = TestableNetworkCallback()
@@ -247,7 +253,7 @@
// IngressDiscardRule is added to the VPN address since the VPN address is not duplicated
// with the Wi-Fi address
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
// The Wi-Fi address is changed back to the same address as the VPN interface
wifiAgent.sendLinkProperties(wifiLp)
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
similarity index 69%
rename from tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
rename to tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
index 9024641..88c2738 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
@@ -24,6 +24,7 @@
import android.net.NativeNetworkType
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
@@ -41,10 +42,13 @@
import android.util.ArraySet
import com.android.net.module.util.CollectionUtils
import com.android.server.ConnectivityService.PREFERENCE_ORDER_SATELLITE_FALLBACK
+import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.TestableNetworkCallback
import com.android.testutils.visibleOnHandlerThread
import org.junit.Assert
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
@@ -61,7 +65,10 @@
@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
-class CSSatelliteNetworkPreferredTest : CSTest() {
+class CSSatelliteNetworkTest : CSTest() {
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
+
/**
* Test createMultiLayerNrisFromSatelliteNetworkPreferredUids returns correct
* NetworkRequestInfo.
@@ -80,54 +87,81 @@
}
/**
- * Test that SATELLITE_NETWORK_PREFERENCE_UIDS changes will send correct net id and uid ranges
- * to netd.
+ * Test that satellite network satisfies satellite fallback per-app default network request and
+ * send correct net id and uid ranges to netd.
*/
- @Test
- fun testSatelliteNetworkPreferredUidsChanged() {
+ private fun doTestSatelliteNetworkFallbackUids(restricted: Boolean) {
val netdInOrder = inOrder(netd)
- val satelliteAgent = createSatelliteAgent("satellite0")
+ val satelliteAgent = createSatelliteAgent("satellite0", restricted)
satelliteAgent.connect()
val satelliteNetId = satelliteAgent.network.netId
+ val permission = if (restricted) {INetd.PERMISSION_SYSTEM} else {INetd.PERMISSION_NONE}
netdInOrder.verify(netd).networkCreate(
- nativeNetworkConfigPhysical(satelliteNetId, INetd.PERMISSION_NONE))
+ nativeNetworkConfigPhysical(satelliteNetId, permission))
val uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
val uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2)
val uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
- // Initial satellite network preferred uids status.
- setAndUpdateSatelliteNetworkPreferredUids(setOf())
+ // Initial satellite network fallback uids status.
+ updateSatelliteNetworkFallbackUids(setOf())
netdInOrder.verify(netd, never()).networkAddUidRangesParcel(any())
netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
- // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting and verify that net id and uid ranges
- // send to netd
+ // Update satellite network fallback uids and verify that net id and uid ranges send to netd
var uids = mutableSetOf(uid1, uid2, uid3)
val uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids))
val config1 = NativeUidRangeConfig(
satelliteNetId, uidRanges1,
PREFERENCE_ORDER_SATELLITE_FALLBACK
)
- setAndUpdateSatelliteNetworkPreferredUids(uids)
+ updateSatelliteNetworkFallbackUids(uids)
netdInOrder.verify(netd).networkAddUidRangesParcel(config1)
netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
- // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting again and verify that old rules are removed
- // and new rules are added.
+ // Update satellite network fallback uids and verify that net id and uid ranges send to netd
uids = mutableSetOf(uid1)
val uidRanges2: Array<UidRangeParcel?> = toUidRangeStableParcels(uidRangesForUids(uids))
val config2 = NativeUidRangeConfig(
satelliteNetId, uidRanges2,
PREFERENCE_ORDER_SATELLITE_FALLBACK
)
- setAndUpdateSatelliteNetworkPreferredUids(uids)
+ updateSatelliteNetworkFallbackUids(uids)
netdInOrder.verify(netd).networkRemoveUidRangesParcel(config1)
netdInOrder.verify(netd).networkAddUidRangesParcel(config2)
}
+ @Test
+ fun testSatelliteNetworkFallbackUids_restricted() {
+ doTestSatelliteNetworkFallbackUids(restricted = true)
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testSatelliteNetworkFallbackUids_nonRestricted() {
+ doTestSatelliteNetworkFallbackUids(restricted = false)
+ }
+
+ private fun doTestSatelliteNeverBecomeDefaultNetwork(restricted: Boolean) {
+ val agent = createSatelliteAgent("satellite0", restricted)
+ agent.connect()
+ val defaultCb = TestableNetworkCallback()
+ cm.registerDefaultNetworkCallback(defaultCb)
+ // Satellite network must not become the default network
+ defaultCb.assertNoCallback()
+ }
+
+ @Test
+ fun testSatelliteNeverBecomeDefaultNetwork_restricted() {
+ doTestSatelliteNeverBecomeDefaultNetwork(restricted = true)
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testSatelliteNeverBecomeDefaultNetwork_notRestricted() {
+ doTestSatelliteNeverBecomeDefaultNetwork(restricted = false)
+ }
+
private fun assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(uids: Set<Int>) {
val nris: Set<ConnectivityService.NetworkRequestInfo> =
service.createMultiLayerNrisFromSatelliteNetworkFallbackUids(uids)
@@ -140,7 +174,7 @@
assertEquals(PREFERENCE_ORDER_SATELLITE_FALLBACK, nri.mPreferenceOrder)
}
- private fun setAndUpdateSatelliteNetworkPreferredUids(uids: Set<Int>) {
+ private fun updateSatelliteNetworkFallbackUids(uids: Set<Int>) {
visibleOnHandlerThread(csHandler) {
deps.satelliteNetworkFallbackUidUpdate!!.accept(uids)
}
@@ -150,9 +184,9 @@
NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL, permission,
false /* secure */, VpnManager.TYPE_VPN_NONE, false /* excludeLocalRoutes */)
- private fun createSatelliteAgent(name: String): CSAgentWrapper {
+ private fun createSatelliteAgent(name: String, restricted: Boolean = true): CSAgentWrapper {
return Agent(score = keepScore(), lp = lp(name),
- nc = nc(TRANSPORT_SATELLITE, NET_CAPABILITY_INTERNET)
+ nc = satelliteNc(restricted)
)
}
@@ -176,17 +210,19 @@
return uidRangesForUids(*CollectionUtils.toIntArray(uids))
}
- private fun nc(transport: Int, vararg caps: Int) = NetworkCapabilities.Builder().apply {
- addTransportType(transport)
- caps.forEach {
- addCapability(it)
- }
- // Useful capabilities for everybody
- addCapability(NET_CAPABILITY_NOT_RESTRICTED)
- addCapability(NET_CAPABILITY_NOT_SUSPENDED)
- addCapability(NET_CAPABILITY_NOT_ROAMING)
- addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
- }.build()
+ private fun satelliteNc(restricted: Boolean) =
+ NetworkCapabilities.Builder().apply {
+ addTransportType(TRANSPORT_SATELLITE)
+
+ addCapability(NET_CAPABILITY_INTERNET)
+ addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ addCapability(NET_CAPABILITY_NOT_ROAMING)
+ addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ if (restricted) {
+ removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ }
+ removeCapability(NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
+ }.build()
private fun lp(iface: String) = LinkProperties().apply {
interfaceName = iface
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index 3b06ad0..ed72fd2 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -27,6 +27,7 @@
import android.content.res.Resources
import android.net.ConnectivityManager
import android.net.INetd
+import android.net.INetd.PERMISSION_INTERNET
import android.net.InetAddresses
import android.net.LinkProperties
import android.net.LocalNetworkConfig
@@ -89,6 +90,7 @@
import org.junit.Rule
import org.junit.rules.TestName
import org.mockito.AdditionalAnswers.delegatesTo
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
@@ -157,11 +159,12 @@
it[ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER] = true
it[ConnectivityFlags.REQUEST_RESTRICTED_WIFI] = true
it[ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION] = true
- it[ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION] = true
it[ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS] = true
it[ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK] = true
it[ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING] = true
it[ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN] = true
+ it[ConnectivityFlags.DELAY_DESTROY_SOCKETS] = true
+ it[ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS] = true
}
fun setFeatureEnabled(flag: String, enabled: Boolean) = enabledFeatures.set(flag, enabled)
@@ -171,7 +174,11 @@
val contentResolver = makeMockContentResolver(context)
val PRIMARY_USER = 0
- val PRIMARY_USER_INFO = UserInfo(PRIMARY_USER, "" /* name */, UserInfo.FLAG_PRIMARY)
+ val PRIMARY_USER_INFO = UserInfo(
+ PRIMARY_USER,
+ "", // name
+ UserInfo.FLAG_PRIMARY
+ )
val PRIMARY_USER_HANDLE = UserHandle(PRIMARY_USER)
val userManager = makeMockUserManager(PRIMARY_USER_INFO, PRIMARY_USER_HANDLE)
val activityManager = makeActivityManager()
@@ -183,10 +190,16 @@
val connResources = makeMockConnResources(sysResources, packageManager)
val netd = mock<INetd>()
- val bpfNetMaps = mock<BpfNetMaps>()
+ val bpfNetMaps = mock<BpfNetMaps>().also {
+ doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt())
+ }
val clatCoordinator = mock<ClatCoordinator>()
val networkRequestStateStatsMetrics = mock<NetworkRequestStateStatsMetrics>()
- val proxyTracker = ProxyTracker(context, mock<Handler>(), 16 /* EVENT_PROXY_HAS_CHANGED */)
+ val proxyTracker = ProxyTracker(
+ context,
+ mock<Handler>(),
+ 16 // EVENT_PROXY_HAS_CHANGED
+ )
val systemConfigManager = makeMockSystemConfigManager()
val batteryStats = mock<IBatteryStats>()
val batteryManager = BatteryStatsManager(batteryStats)
@@ -198,6 +211,7 @@
val multicastRoutingCoordinatorService = mock<MulticastRoutingCoordinatorService>()
val satelliteAccessController = mock<SatelliteAccessController>()
+ val destroySocketsWrapper = mock<DestroySocketsWrapper>()
val deps = CSDeps()
@@ -251,6 +265,11 @@
alarmHandlerThread.join()
}
+ // Class to be mocked and used to verify destroy sockets methods call
+ open inner class DestroySocketsWrapper {
+ open fun destroyLiveTcpSocketsByOwnerUids(ownerUids: Set<Int>) {}
+ }
+
inner class CSDeps : ConnectivityService.Dependencies() {
override fun getResources(ctx: Context) = connResources
override fun getBpfNetMaps(context: Context, netd: INetd) = this@CSTest.bpfNetMaps
@@ -356,6 +375,11 @@
override fun getCallingUid() =
if (callingUid == CALLING_UID_UNMOCKED) super.getCallingUid() else callingUid
+
+ override fun destroyLiveTcpSocketsByOwnerUids(ownerUids: Set<Int>) {
+ // Call mocked destroyLiveTcpSocketsByOwnerUids so that test can verify this method call
+ destroySocketsWrapper.destroyLiveTcpSocketsByOwnerUids(ownerUids)
+ }
}
inner class CSContext(base: Context) : BroadcastInterceptingContext(base) {
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetInterfaceStateMachineTest.kt b/tests/unit/java/com/android/server/ethernet/EthernetInterfaceStateMachineTest.kt
new file mode 100644
index 0000000..c8b2f65
--- /dev/null
+++ b/tests/unit/java/com/android/server/ethernet/EthernetInterfaceStateMachineTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+// ktlint does not allow annotating function argument literals inline. Disable the specific rule
+// since this negatively affects readability.
+@file:Suppress("ktlint:standard:comment-wrapping")
+
+package com.android.server.ethernet
+
+import android.content.Context
+import android.net.NetworkCapabilities
+import android.net.NetworkProvider
+import android.net.NetworkProvider.NetworkOfferCallback
+import android.os.Build
+import android.os.Handler
+import android.os.test.TestLooper
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+private const val IFACE = "eth0"
+private val CAPS = NetworkCapabilities.Builder().build()
+
+@SmallTest
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class EthernetInterfaceStateMachineTest {
+ private lateinit var looper: TestLooper
+ private lateinit var handler: Handler
+ private lateinit var ifaceState: EthernetInterfaceStateMachine
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var provider: NetworkProvider
+ @Mock private lateinit var deps: EthernetNetworkFactory.Dependencies
+
+ // There seems to be no (obvious) way to force execution of @Before and @Test annotation on the
+ // same thread. Since SyncStateMachine requires all interactions to be called from the same
+ // thread that is provided at construction time (in this case, the thread that TestLooper() is
+ // called on), setUp() must be called directly from the @Test method.
+ // TODO: find a way to fix this in the test runner.
+ fun setUp() {
+ looper = TestLooper()
+ handler = Handler(looper.looper)
+ MockitoAnnotations.initMocks(this)
+
+ ifaceState = EthernetInterfaceStateMachine(IFACE, handler, context, CAPS, provider, deps)
+ }
+
+ @Test
+ fun testUpdateLinkState_networkOfferRegisteredAndRetracted() {
+ setUp()
+
+ ifaceState.updateLinkState(/* up= */ true)
+
+ // link comes up: validate the NetworkOffer is registered and capture callback object.
+ val inOrder = inOrder(provider)
+ val networkOfferCb = ArgumentCaptor.forClass(NetworkOfferCallback::class.java).also {
+ inOrder.verify(provider).registerNetworkOffer(any(), any(), any(), it.capture())
+ }.value
+
+ ifaceState.updateLinkState(/* up */ false)
+
+ // link goes down: validate the NetworkOffer is retracted
+ inOrder.verify(provider).unregisterNetworkOffer(eq(networkOfferCb))
+ }
+}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index a19e4e7..7e0a225 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -56,6 +56,7 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.net.TrafficStats.UID_REMOVED;
import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
@@ -310,6 +311,7 @@
private HandlerThread mObserverHandlerThread;
final TestDependencies mDeps = new TestDependencies();
final HashMap<String, Boolean> mFeatureFlags = new HashMap<>();
+ final HashMap<Long, Boolean> mCompatChanges = new HashMap<>();
// This will set feature flags from @FeatureFlag annotations
// into the map before setUp() runs.
@@ -318,7 +320,7 @@
new SetFeatureFlagsRule((name, enabled) -> {
mFeatureFlags.put(name, enabled);
return null;
- });
+ }, (name) -> mFeatureFlags.getOrDefault(name, false));
private class MockContext extends BroadcastInterceptingContext {
private final Context mBaseContext;
@@ -611,7 +613,7 @@
}
@Override
- public boolean supportTrafficStatsRateLimitCache(Context ctx) {
+ public boolean alwaysUseTrafficStatsRateLimitCache(Context ctx) {
return mFeatureFlags.getOrDefault(TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG, false);
}
@@ -625,6 +627,14 @@
return DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES;
}
+ @Override
+ public boolean isChangeEnabled(long changeId, int uid) {
+ return mCompatChanges.getOrDefault(changeId, true);
+ }
+
+ public void setChangeEnabled(long changeId, boolean enabled) {
+ mCompatChanges.put(changeId, enabled);
+ }
@Nullable
@Override
public NetworkStats.Entry nativeGetTotalStat() {
@@ -2430,17 +2440,33 @@
@FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
@Test
- public void testTrafficStatsRateLimitCache_disabled() throws Exception {
- doTestTrafficStatsRateLimitCache(false /* cacheEnabled */);
+ public void testTrafficStatsRateLimitCache_disabledWithCompatChangeEnabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, true);
+ doTestTrafficStatsRateLimitCache(true /* expectCached */);
}
@FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG)
@Test
- public void testTrafficStatsRateLimitCache_enabled() throws Exception {
- doTestTrafficStatsRateLimitCache(true /* cacheEnabled */);
+ public void testTrafficStatsRateLimitCache_enabledWithCompatChangeEnabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, true);
+ doTestTrafficStatsRateLimitCache(true /* expectCached */);
}
- private void doTestTrafficStatsRateLimitCache(boolean cacheEnabled) throws Exception {
+ @FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
+ @Test
+ public void testTrafficStatsRateLimitCache_disabledWithCompatChangeDisabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, false);
+ doTestTrafficStatsRateLimitCache(false /* expectCached */);
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG)
+ @Test
+ public void testTrafficStatsRateLimitCache_enabledWithCompatChangeDisabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, false);
+ doTestTrafficStatsRateLimitCache(true /* expectCached */);
+ }
+
+ private void doTestTrafficStatsRateLimitCache(boolean expectCached) throws Exception {
mockDefaultSettings();
// Calling uid is not injected into the service, use the real uid to pass the caller check.
final int myUid = Process.myUid();
@@ -2450,7 +2476,7 @@
// Verify the values are cached.
incrementCurrentTime(DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS / 2);
mockTrafficStatsValues(65L, 8L, 1055L, 9L);
- if (cacheEnabled) {
+ if (expectCached) {
assertTrafficStatsValues(TEST_IFACE, myUid, 64L, 3L, 1024L, 8L);
} else {
assertTrafficStatsValues(TEST_IFACE, myUid, 65L, 8L, 1055L, 9L);
diff --git a/thread/README.md b/thread/README.md
index f50e0cd..41b73ac 100644
--- a/thread/README.md
+++ b/thread/README.md
@@ -1,3 +1,18 @@
# Thread
Bring the [Thread](https://www.threadgroup.org/) networking protocol to Android.
+
+## Try Thread with Cuttlefish
+
+```
+# Get the code and go to the Android source code root directory
+
+source build/envsetup.sh
+lunch aosp_cf_x86_64_phone-trunk_staging-userdebug
+m
+
+launch_cvd
+```
+
+Open `https://localhost:8443/` in your web browser, you can find the Thread
+demoapp (with the Thread logo) in the cuttlefish instance. Open it and have fun with Thread!
diff --git a/thread/framework/java/android/net/thread/IConfigurationReceiver.aidl b/thread/framework/java/android/net/thread/IConfigurationReceiver.aidl
new file mode 100644
index 0000000..dcc4545
--- /dev/null
+++ b/thread/framework/java/android/net/thread/IConfigurationReceiver.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 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 android.net.thread;
+
+import android.net.thread.ThreadConfiguration;
+
+/** Receives the result of a Thread Configuration change. @hide */
+oneway interface IConfigurationReceiver {
+ void onConfigurationChanged(in ThreadConfiguration configuration);
+}
diff --git a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
index c5ca557..f50de74 100644
--- a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
+++ b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
@@ -19,11 +19,13 @@
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.ChannelMaxPower;
import android.net.thread.IActiveOperationalDatasetReceiver;
-import android.net.thread.IOperationalDatasetCallback;
+import android.net.thread.IConfigurationReceiver;
import android.net.thread.IOperationReceiver;
+import android.net.thread.IOperationalDatasetCallback;
import android.net.thread.IScheduleMigrationReceiver;
import android.net.thread.IStateCallback;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.ThreadConfiguration;
/**
* Interface for communicating with ThreadNetworkControllerService.
@@ -46,4 +48,7 @@
void createRandomizedDataset(String networkName, IActiveOperationalDatasetReceiver receiver);
void setEnabled(boolean enabled, in IOperationReceiver receiver);
+ void setConfiguration(in ThreadConfiguration config, in IOperationReceiver receiver);
+ void registerConfigurationCallback(in IConfigurationReceiver receiver);
+ void unregisterConfigurationCallback(in IConfigurationReceiver receiver);
}
diff --git a/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java b/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
index 520acbd..cecb4e9 100644
--- a/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
+++ b/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
@@ -65,11 +65,32 @@
*/
@NonNull
public static OperationalDatasetTimestamp fromInstant(@NonNull Instant instant) {
+ return OperationalDatasetTimestamp.fromInstant(instant, true /* isAuthoritativeSource */);
+ }
+
+ /**
+ * Creates a new {@link OperationalDatasetTimestamp} object from an {@link Instant}.
+ *
+ * <p>The {@code seconds} is set to {@code instant.getEpochSecond()}, {@code ticks} is set to
+ * {@link instant#getNano()} based on frequency of 32768 Hz, and {@code isAuthoritativeSource}
+ * is set to {@code isAuthoritativeSource}.
+ *
+ * <p>Note that this conversion can lose precision and a value returned by {@link #toInstant}
+ * may not equal exactly the {@code instant}.
+ *
+ * @throws IllegalArgumentException if {@code instant.getEpochSecond()} is larger than {@code
+ * 0xffffffffffffL}
+ * @see toInstant
+ * @hide
+ */
+ @NonNull
+ public static OperationalDatasetTimestamp fromInstant(
+ @NonNull Instant instant, boolean isAuthoritativeSource) {
int ticks = getRoundedTicks(instant.getNano());
long seconds = instant.getEpochSecond() + ticks / TICKS_UPPER_BOUND;
// the rounded ticks can be 0x8000 if instant.getNano() >= 999984742
ticks = ticks % TICKS_UPPER_BOUND;
- return new OperationalDatasetTimestamp(seconds, ticks, true /* isAuthoritativeSource */);
+ return new OperationalDatasetTimestamp(seconds, ticks, isAuthoritativeSource);
}
/**
diff --git a/thread/framework/java/android/net/thread/ThreadConfiguration.aidl b/thread/framework/java/android/net/thread/ThreadConfiguration.aidl
new file mode 100644
index 0000000..9473411
--- /dev/null
+++ b/thread/framework/java/android/net/thread/ThreadConfiguration.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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 android.net.thread;
+
+parcelable ThreadConfiguration;
diff --git a/thread/framework/java/android/net/thread/ThreadConfiguration.java b/thread/framework/java/android/net/thread/ThreadConfiguration.java
new file mode 100644
index 0000000..e09b3a6
--- /dev/null
+++ b/thread/framework/java/android/net/thread/ThreadConfiguration.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 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 android.net.thread;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Data interface for Thread device configuration.
+ *
+ * <p>An example usage of creating a {@link ThreadConfiguration} that turns on NAT64 feature based
+ * on an existing {@link ThreadConfiguration}:
+ *
+ * <pre>{@code
+ * ThreadConfiguration config =
+ * new ThreadConfiguration.Builder(existingConfig).setNat64Enabled(true).build();
+ * }</pre>
+ *
+ * @see ThreadNetworkController#setConfiguration
+ * @see ThreadNetworkController#registerConfigurationCallback
+ * @see ThreadNetworkController#unregisterConfigurationCallback
+ * @hide
+ */
+// @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+// @SystemApi
+public final class ThreadConfiguration implements Parcelable {
+ private final boolean mNat64Enabled;
+ private final boolean mDhcp6PdEnabled;
+
+ private ThreadConfiguration(Builder builder) {
+ this(builder.mNat64Enabled, builder.mDhcp6PdEnabled);
+ }
+
+ private ThreadConfiguration(boolean nat64Enabled, boolean dhcp6PdEnabled) {
+ this.mNat64Enabled = nat64Enabled;
+ this.mDhcp6PdEnabled = dhcp6PdEnabled;
+ }
+
+ /** Returns {@code true} if NAT64 is enabled. */
+ public boolean isNat64Enabled() {
+ return mNat64Enabled;
+ }
+
+ /** Returns {@code true} if DHCPv6 Prefix Delegation is enabled. */
+ public boolean isDhcp6PdEnabled() {
+ return mDhcp6PdEnabled;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (!(other instanceof ThreadConfiguration)) {
+ return false;
+ } else {
+ ThreadConfiguration otherConfig = (ThreadConfiguration) other;
+ return mNat64Enabled == otherConfig.mNat64Enabled
+ && mDhcp6PdEnabled == otherConfig.mDhcp6PdEnabled;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNat64Enabled, mDhcp6PdEnabled);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ sb.append("Nat64Enabled=").append(mNat64Enabled);
+ sb.append(", Dhcp6PdEnabled=").append(mDhcp6PdEnabled);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBoolean(mNat64Enabled);
+ dest.writeBoolean(mDhcp6PdEnabled);
+ }
+
+ public static final @NonNull Creator<ThreadConfiguration> CREATOR =
+ new Creator<>() {
+ @Override
+ public ThreadConfiguration createFromParcel(Parcel in) {
+ ThreadConfiguration.Builder builder = new ThreadConfiguration.Builder();
+ builder.setNat64Enabled(in.readBoolean());
+ builder.setDhcp6PdEnabled(in.readBoolean());
+ return builder.build();
+ }
+
+ @Override
+ public ThreadConfiguration[] newArray(int size) {
+ return new ThreadConfiguration[size];
+ }
+ };
+
+ /** The builder for creating {@link ThreadConfiguration} objects. */
+ public static final class Builder {
+ private boolean mNat64Enabled = false;
+ private boolean mDhcp6PdEnabled = false;
+
+ /** Creates a new {@link Builder} object with all features disabled. */
+ public Builder() {}
+
+ /**
+ * Creates a new {@link Builder} object from a {@link ThreadConfiguration} object.
+ *
+ * @param config the Border Router configurations to be copied
+ */
+ public Builder(@NonNull ThreadConfiguration config) {
+ Objects.requireNonNull(config);
+
+ mNat64Enabled = config.mNat64Enabled;
+ mDhcp6PdEnabled = config.mDhcp6PdEnabled;
+ }
+
+ /**
+ * Enables or disables NAT64 for the device.
+ *
+ * <p>Enabling this feature will allow Thread devices to connect to the internet/cloud over
+ * IPv4.
+ */
+ @NonNull
+ public Builder setNat64Enabled(boolean enabled) {
+ this.mNat64Enabled = enabled;
+ return this;
+ }
+
+ /**
+ * Enables or disables Prefix Delegation for the device.
+ *
+ * <p>Enabling this feature will allow Thread devices to connect to the internet/cloud over
+ * IPv6.
+ */
+ @NonNull
+ public Builder setDhcp6PdEnabled(boolean enabled) {
+ this.mDhcp6PdEnabled = enabled;
+ return this;
+ }
+
+ /** Creates a new {@link ThreadConfiguration} object. */
+ @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 8d6b40a..30b3d6a 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -41,6 +41,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Provides the primary APIs for controlling all aspects of a Thread network.
@@ -124,6 +125,12 @@
private final Map<OperationalDatasetCallback, OperationalDatasetCallbackProxy>
mOpDatasetCallbackMap = new HashMap<>();
+ private final Object mConfigurationCallbackMapLock = new Object();
+
+ @GuardedBy("mConfigurationCallbackMapLock")
+ private final Map<Consumer<ThreadConfiguration>, ConfigurationCallbackProxy>
+ mConfigurationCallbackMap = new HashMap<>();
+
/** @hide */
public ThreadNetworkController(@NonNull IThreadNetworkController controllerService) {
requireNonNull(controllerService, "controllerService cannot be null");
@@ -579,6 +586,97 @@
}
/**
+ * Configures the Thread features for this device.
+ *
+ * <p>This method sets the {@link ThreadConfiguration} for this device. On success, the {@link
+ * 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.
+ *
+ * @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)
+ public void setConfiguration(
+ @NonNull ThreadConfiguration configuration,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
+ requireNonNull(configuration, "Configuration cannot be null");
+ requireNonNull(executor, "executor cannot be null");
+ requireNonNull(receiver, "receiver cannot be null");
+ try {
+ mControllerService.setConfiguration(
+ configuration, new OperationReceiverProxy(executor, receiver));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a callback to be called when the configuration is changed.
+ *
+ * <p>Upon return of this method, {@code callback} will be invoked immediately with the new
+ * {@link ThreadConfiguration}.
+ *
+ * @param executor the executor to execute the {@code callback}
+ * @param callback the callback to receive Thread configuration changes
+ * @throws IllegalArgumentException if {@code callback} has already been registered
+ * @hide
+ */
+ // @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+ // @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
+ public void registerConfigurationCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<ThreadConfiguration> callback) {
+ requireNonNull(executor, "executor cannot be null");
+ requireNonNull(callback, "callback cannot be null");
+ synchronized (mConfigurationCallbackMapLock) {
+ if (mConfigurationCallbackMap.containsKey(callback)) {
+ throw new IllegalArgumentException("callback has already been registered");
+ }
+ ConfigurationCallbackProxy callbackProxy =
+ new ConfigurationCallbackProxy(executor, callback);
+ mConfigurationCallbackMap.put(callback, callbackProxy);
+ try {
+ mControllerService.registerConfigurationCallback(callbackProxy);
+ } catch (RemoteException e) {
+ mConfigurationCallbackMap.remove(callback);
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Unregisters the configuration callback.
+ *
+ * @param callback the callback which has been registered with {@link
+ * #registerConfigurationCallback}
+ * @throws IllegalArgumentException if {@code callback} hasn't been registered
+ * @hide
+ */
+ // @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+ // @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
+ public void unregisterConfigurationCallback(@NonNull Consumer<ThreadConfiguration> callback) {
+ requireNonNull(callback, "callback cannot be null");
+ synchronized (mConfigurationCallbackMapLock) {
+ ConfigurationCallbackProxy callbackProxy = mConfigurationCallbackMap.get(callback);
+ if (callbackProxy == null) {
+ throw new IllegalArgumentException("callback hasn't been registered");
+ }
+ try {
+ mControllerService.unregisterConfigurationCallback(callbackProxy);
+ mConfigurationCallbackMap.remove(callbackProxy.mConfigurationConsumer);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Sets to use a specified test network as the upstream.
*
* @param testNetworkInterfaceName The name of the test network interface. When it's null,
@@ -764,4 +862,26 @@
propagateError(mExecutor, mResultReceiver, errorCode, errorMessage);
}
}
+
+ private static final class ConfigurationCallbackProxy extends IConfigurationReceiver.Stub {
+ final Executor mExecutor;
+ final Consumer<ThreadConfiguration> mConfigurationConsumer;
+
+ ConfigurationCallbackProxy(
+ @CallbackExecutor Executor executor,
+ Consumer<ThreadConfiguration> ConfigurationConsumer) {
+ this.mExecutor = executor;
+ this.mConfigurationConsumer = ConfigurationConsumer;
+ }
+
+ @Override
+ public void onConfigurationChanged(ThreadConfiguration configuration) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mConfigurationConsumer.accept(configuration));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
}
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkFlags.java b/thread/framework/java/android/net/thread/ThreadNetworkFlags.java
index e6ab988..691bbf5 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkFlags.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkFlags.java
@@ -27,5 +27,9 @@
/** @hide */
public static final String FLAG_THREAD_ENABLED = "com.android.net.thread.flags.thread_enabled";
+ /** @hide */
+ public static final String FLAG_CONFIGURATION_ENABLED =
+ "com.android.net.thread.flags.configuration_enabled";
+
private ThreadNetworkFlags() {}
}
diff --git a/thread/service/java/com/android/server/thread/InfraInterfaceController.java b/thread/service/java/com/android/server/thread/InfraInterfaceController.java
index be54cbc..e72c9ee 100644
--- a/thread/service/java/com/android/server/thread/InfraInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/InfraInterfaceController.java
@@ -16,14 +16,30 @@
package com.android.server.thread;
-import android.os.ParcelFileDescriptor;
+import static android.system.OsConstants.IPPROTO_IPV6;
+import static android.system.OsConstants.IPPROTO_RAW;
+import static android.system.OsConstants.IPV6_CHECKSUM;
+import static android.system.OsConstants.IPV6_MULTICAST_HOPS;
+import static android.system.OsConstants.IPV6_RECVHOPLIMIT;
+import static android.system.OsConstants.IPV6_RECVPKTINFO;
+import static android.system.OsConstants.IPV6_UNICAST_HOPS;
+import android.net.util.SocketUtils;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import java.io.FileDescriptor;
import java.io.IOException;
/** Controller for the infrastructure network interface. */
public class InfraInterfaceController {
private static final String TAG = "InfraIfController";
+ private static final int ENABLE = 1;
+ private static final int IPV6_CHECKSUM_OFFSET = 2;
+ private static final int HOP_LIMIT = 255;
+
static {
System.loadLibrary("service-thread-jni");
}
@@ -37,8 +53,21 @@
* @throws IOException when fails to create the socket.
*/
public ParcelFileDescriptor createIcmp6Socket(String infraInterfaceName) throws IOException {
- return ParcelFileDescriptor.adoptFd(nativeCreateIcmp6Socket(infraInterfaceName));
+ ParcelFileDescriptor parcelFd =
+ ParcelFileDescriptor.adoptFd(nativeCreateFilteredIcmp6Socket());
+ FileDescriptor fd = parcelFd.getFileDescriptor();
+ try {
+ Os.setsockoptInt(fd, IPPROTO_RAW, IPV6_CHECKSUM, IPV6_CHECKSUM_OFFSET);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, ENABLE);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, ENABLE);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, HOP_LIMIT);
+ Os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, HOP_LIMIT);
+ SocketUtils.bindSocketToInterface(fd, infraInterfaceName);
+ } catch (ErrnoException e) {
+ throw new IOException("Failed to setsockopt for the ICMPv6 socket", e);
+ }
+ return parcelFd;
}
- private static native int nativeCreateIcmp6Socket(String interfaceName) throws IOException;
+ private static native int nativeCreateFilteredIcmp6Socket() throws IOException;
}
diff --git a/thread/service/java/com/android/server/thread/NsdPublisher.java b/thread/service/java/com/android/server/thread/NsdPublisher.java
index 2c14f1d..1447ff8 100644
--- a/thread/service/java/com/android/server/thread/NsdPublisher.java
+++ b/thread/service/java/com/android/server/thread/NsdPublisher.java
@@ -19,11 +19,15 @@
import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
+import android.net.DnsResolver;
import android.net.InetAddresses;
+import android.net.Network;
import android.net.nsd.DiscoveryRequest;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.RemoteException;
import android.text.TextUtils;
@@ -34,13 +38,14 @@
import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.INsdDiscoverServiceCallback;
import com.android.server.thread.openthread.INsdPublisher;
+import com.android.server.thread.openthread.INsdResolveHostCallback;
import com.android.server.thread.openthread.INsdResolveServiceCallback;
import com.android.server.thread.openthread.INsdStatusReceiver;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -56,24 +61,36 @@
* {@code mHandler} itself.
*/
public final class NsdPublisher extends INsdPublisher.Stub {
- // TODO: b/321883491 - specify network for mDNS operations
private static final String TAG = NsdPublisher.class.getSimpleName();
+
+ // TODO: b/321883491 - specify network for mDNS operations
+ @Nullable private Network mNetwork;
private final NsdManager mNsdManager;
+ private final DnsResolver mDnsResolver;
private final Handler mHandler;
private final Executor mExecutor;
private final SparseArray<RegistrationListener> mRegistrationListeners = new SparseArray<>(0);
private final SparseArray<DiscoveryListener> mDiscoveryListeners = new SparseArray<>(0);
private final SparseArray<ServiceInfoListener> mServiceInfoListeners = new SparseArray<>(0);
+ private final SparseArray<HostInfoListener> mHostInfoListeners = new SparseArray<>(0);
@VisibleForTesting
- public NsdPublisher(NsdManager nsdManager, Handler handler) {
+ public NsdPublisher(NsdManager nsdManager, DnsResolver dnsResolver, Handler handler) {
+ mNetwork = null;
mNsdManager = nsdManager;
+ mDnsResolver = dnsResolver;
mHandler = handler;
mExecutor = runnable -> mHandler.post(runnable);
}
public static NsdPublisher newInstance(Context context, Handler handler) {
- return new NsdPublisher(context.getSystemService(NsdManager.class), handler);
+ return new NsdPublisher(
+ context.getSystemService(NsdManager.class), DnsResolver.getInstance(), handler);
+ }
+
+ // TODO: b/321883491 - NsdPublisher should be disabled when mNetwork is null
+ public void setNetworkForHostResolution(@Nullable Network network) {
+ mNetwork = network;
}
@Override
@@ -291,6 +308,53 @@
}
}
+ @Override
+ public void resolveHost(String name, INsdResolveHostCallback callback, int listenerId) {
+ mHandler.post(() -> resolveHostInternal(name, callback, listenerId));
+ }
+
+ private void resolveHostInternal(
+ String name, INsdResolveHostCallback callback, int listenerId) {
+ checkOnHandlerThread();
+
+ String fullHostname = name + ".local";
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ HostInfoListener listener =
+ new HostInfoListener(name, callback, cancellationSignal, listenerId);
+ mDnsResolver.query(
+ mNetwork,
+ fullHostname,
+ DnsResolver.FLAG_NO_CACHE_LOOKUP,
+ mExecutor,
+ cancellationSignal,
+ listener);
+ mHostInfoListeners.append(listenerId, listener);
+
+ Log.i(TAG, "Resolving host." + " Listener ID: " + listenerId + ", hostname: " + name);
+ }
+
+ @Override
+ public void stopHostResolution(int listenerId) {
+ mHandler.post(() -> stopHostResolutionInternal(listenerId));
+ }
+
+ private void stopHostResolutionInternal(int listenerId) {
+ checkOnHandlerThread();
+
+ HostInfoListener listener = mHostInfoListeners.get(listenerId);
+ if (listener == null) {
+ Log.w(
+ TAG,
+ "Failed to stop host resolution. Listener ID: "
+ + listenerId
+ + ". The listener is null.");
+ return;
+ }
+ Log.i(TAG, "Stopping host resolution. Listener: " + listener);
+ listener.cancel();
+ mHostInfoListeners.remove(listenerId);
+ }
+
private void checkOnHandlerThread() {
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
throw new IllegalStateException(
@@ -550,9 +614,8 @@
}
List<DnsTxtAttribute> txtList = new ArrayList<>();
for (Map.Entry<String, byte[]> entry : serviceInfo.getAttributes().entrySet()) {
- DnsTxtAttribute attribute = new DnsTxtAttribute();
- attribute.name = entry.getKey();
- attribute.value = Arrays.copyOf(entry.getValue(), entry.getValue().length);
+ DnsTxtAttribute attribute =
+ new DnsTxtAttribute(entry.getKey(), entry.getValue().clone());
txtList.add(attribute);
}
// TODO: b/329018320 - Use the serviceInfo.getExpirationTime to derive TTL.
@@ -586,4 +649,78 @@
return "ID: " + mListenerId + ", service name: " + mName + ", service type: " + mType;
}
}
+
+ class HostInfoListener implements DnsResolver.Callback<List<InetAddress>> {
+ private final String mHostname;
+ private final INsdResolveHostCallback mResolveHostCallback;
+ private final CancellationSignal mCancellationSignal;
+ private final int mListenerId;
+
+ HostInfoListener(
+ @NonNull String hostname,
+ INsdResolveHostCallback resolveHostCallback,
+ CancellationSignal cancellationSignal,
+ int listenerId) {
+ this.mHostname = hostname;
+ this.mResolveHostCallback = resolveHostCallback;
+ this.mCancellationSignal = cancellationSignal;
+ this.mListenerId = listenerId;
+ }
+
+ @Override
+ public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
+ checkOnHandlerThread();
+
+ Log.i(
+ TAG,
+ "Host is resolved."
+ + " Listener ID: "
+ + mListenerId
+ + ", hostname: "
+ + mHostname
+ + ", addresses: "
+ + answerList
+ + ", return code: "
+ + rcode);
+ List<String> addressStrings = new ArrayList<>();
+ for (InetAddress address : answerList) {
+ addressStrings.add(address.getHostAddress());
+ }
+ try {
+ mResolveHostCallback.onHostResolved(mHostname, addressStrings);
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ mHostInfoListeners.remove(mListenerId);
+ }
+
+ @Override
+ public void onError(@NonNull DnsResolver.DnsException error) {
+ checkOnHandlerThread();
+
+ Log.i(
+ TAG,
+ "Failed to resolve host."
+ + " Listener ID: "
+ + mListenerId
+ + ", hostname: "
+ + mHostname,
+ error);
+ try {
+ mResolveHostCallback.onHostResolved(mHostname, Collections.emptyList());
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ mHostInfoListeners.remove(mListenerId);
+ }
+
+ public String toString() {
+ return "ID: " + mListenerId + ", hostname: " + mHostname;
+ }
+
+ void cancel() {
+ mCancellationSignal.cancel();
+ mHostInfoListeners.remove(mListenerId);
+ }
+ }
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 0c200fd..2f60d9a 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -61,6 +61,8 @@
import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_ENABLED;
import static com.android.server.thread.openthread.IOtDaemon.TUN_IF_NAME;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -89,12 +91,14 @@
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
import android.net.thread.ChannelMaxPower;
import android.net.thread.IActiveOperationalDatasetReceiver;
+import android.net.thread.IConfigurationReceiver;
import android.net.thread.IOperationReceiver;
import android.net.thread.IOperationalDatasetCallback;
import android.net.thread.IStateCallback;
import android.net.thread.IThreadNetworkController;
import android.net.thread.OperationalDatasetTimestamp;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.ThreadConfiguration;
import android.net.thread.ThreadNetworkController;
import android.net.thread.ThreadNetworkController.DeviceRole;
import android.net.thread.ThreadNetworkException;
@@ -105,6 +109,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
@@ -116,6 +121,7 @@
import com.android.server.connectivity.ConnectivityResources;
import com.android.server.thread.openthread.BackboneRouterState;
import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
+import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.IChannelMasksReceiver;
import com.android.server.thread.openthread.IOtDaemon;
import com.android.server.thread.openthread.IOtDaemonCallback;
@@ -129,8 +135,9 @@
import java.io.IOException;
import java.net.Inet6Address;
-import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
+import java.time.Clock;
+import java.time.DateTimeException;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
@@ -184,6 +191,8 @@
private final OtDaemonCallbackProxy mOtDaemonCallbackProxy = new OtDaemonCallbackProxy();
private final ConnectivityResources mResources;
private final Supplier<String> mCountryCodeSupplier;
+ private final Map<IConfigurationReceiver, IBinder.DeathRecipient> mConfigurationReceivers =
+ new HashMap<>();
// This should not be directly used for calling IOtDaemon APIs because ot-daemon may die and
// {@code mOtDaemon} will be set to {@code null}. Instead, use {@code getOtDaemon()}
@@ -335,9 +344,11 @@
final String modelName = resources.getString(R.string.config_thread_model_name);
final String vendorName = resources.getString(R.string.config_thread_vendor_name);
final String vendorOui = resources.getString(R.string.config_thread_vendor_oui);
+ final boolean managedByGoogle =
+ resources.getBoolean(R.bool.config_thread_managed_by_google_home);
if (!modelName.isEmpty()) {
- if (modelName.getBytes(StandardCharsets.UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) {
+ if (modelName.getBytes(UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) {
throw new IllegalStateException(
"Model name is longer than "
+ MAX_MODEL_NAME_UTF8_BYTES
@@ -347,7 +358,7 @@
}
if (!vendorName.isEmpty()) {
- if (vendorName.getBytes(StandardCharsets.UTF_8).length > MAX_VENDOR_NAME_UTF8_BYTES) {
+ if (vendorName.getBytes(UTF_8).length > MAX_VENDOR_NAME_UTF8_BYTES) {
throw new IllegalStateException(
"Vendor name is longer than "
+ MAX_VENDOR_NAME_UTF8_BYTES
@@ -364,9 +375,21 @@
meshcopTxts.modelName = modelName;
meshcopTxts.vendorName = vendorName;
meshcopTxts.vendorOui = HexEncoding.decode(vendorOui.replace("-", "").replace(":", ""));
+ meshcopTxts.nonStandardTxtEntries = List.of(makeManagedByGoogleTxtAttr(managedByGoogle));
+
return meshcopTxts;
}
+ /**
+ * Creates a DNS-SD TXT entry for indicating whether Thread on this device is managed by Google.
+ *
+ * @return TXT entry "vgh=1" if {@code managedByGoogle} is {@code true}; otherwise, "vgh=0"
+ */
+ private static DnsTxtAttribute makeManagedByGoogleTxtAttr(boolean managedByGoogle) {
+ final byte[] value = (managedByGoogle ? "1" : "0").getBytes(UTF_8);
+ return new DnsTxtAttribute("vgh", value);
+ }
+
private void onOtDaemonDied() {
checkOnHandlerThread();
Log.w(TAG, "OT daemon is dead, clean up...");
@@ -465,6 +488,7 @@
private void setEnabledInternal(
boolean isEnabled, boolean persist, @NonNull OperationReceiverWrapper receiver) {
+ checkOnHandlerThread();
if (isEnabled && isThreadUserRestricted()) {
receiver.onError(
ERROR_FAILED_PRECONDITION,
@@ -498,17 +522,86 @@
}
}
+ @Override
+ public void setConfiguration(
+ @NonNull ThreadConfiguration configuration, @NonNull IOperationReceiver receiver) {
+ enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
+ mHandler.post(() -> setConfigurationInternal(configuration, receiver));
+ }
+
+ private void setConfigurationInternal(
+ @NonNull ThreadConfiguration configuration,
+ @NonNull IOperationReceiver operationReceiver) {
+ checkOnHandlerThread();
+
+ Log.i(TAG, "Set Thread configuration: " + configuration);
+
+ final boolean changed = mPersistentSettings.putConfiguration(configuration);
+ try {
+ operationReceiver.onSuccess();
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ if (changed) {
+ for (IConfigurationReceiver configReceiver : mConfigurationReceivers.keySet()) {
+ try {
+ configReceiver.onConfigurationChanged(configuration);
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ }
+ }
+ }
+
+ @Override
+ public void registerConfigurationCallback(@NonNull IConfigurationReceiver callback) {
+ enforceAllPermissionsGranted(permission.THREAD_NETWORK_PRIVILEGED);
+ mHandler.post(() -> registerConfigurationCallbackInternal(callback));
+ }
+
+ private void registerConfigurationCallbackInternal(@NonNull IConfigurationReceiver callback) {
+ checkOnHandlerThread();
+ if (mConfigurationReceivers.containsKey(callback)) {
+ throw new IllegalStateException("Registering the same IConfigurationReceiver twice");
+ }
+ IBinder.DeathRecipient deathRecipient =
+ () -> mHandler.post(() -> unregisterConfigurationCallbackInternal(callback));
+ try {
+ callback.asBinder().linkToDeath(deathRecipient, 0);
+ } catch (RemoteException e) {
+ return;
+ }
+ mConfigurationReceivers.put(callback, deathRecipient);
+ try {
+ callback.onConfigurationChanged(mPersistentSettings.getConfiguration());
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ }
+
+ @Override
+ public void unregisterConfigurationCallback(@NonNull IConfigurationReceiver callback) {
+ enforceAllPermissionsGranted(permission.THREAD_NETWORK_PRIVILEGED);
+ mHandler.post(() -> unregisterConfigurationCallbackInternal(callback));
+ }
+
+ private void unregisterConfigurationCallbackInternal(@NonNull IConfigurationReceiver callback) {
+ checkOnHandlerThread();
+ if (!mConfigurationReceivers.containsKey(callback)) {
+ return;
+ }
+ callback.asBinder().unlinkToDeath(mConfigurationReceivers.remove(callback), 0);
+ }
+
private void registerUserRestrictionsReceiver() {
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- onUserRestrictionsChanged(isThreadUserRestricted());
+ mHandler.post(() -> onUserRestrictionsChanged(isThreadUserRestricted()));
}
},
- new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED),
- null /* broadcastPermission */,
- mHandler);
+ new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED));
}
private void onUserRestrictionsChanged(boolean newUserRestrictedState) {
@@ -560,12 +653,10 @@
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- onAirplaneModeChanged(isAirplaneModeOn());
+ mHandler.post(() -> onAirplaneModeChanged(isAirplaneModeOn()));
}
},
- new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED),
- null /* broadcastPermission */,
- mHandler);
+ new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
}
private void onAirplaneModeChanged(boolean newAirplaneModeOn) {
@@ -619,7 +710,10 @@
return !mForceStopOtDaemonEnabled
&& !mUserRestricted
- && (!mAirplaneModeOn || enabledInAirplaneMode)
+ // FIXME(b/340744397): Note that here we need to call `isAirplaneModeOn()` to get
+ // the latest state of airplane mode but can't use `mIsAirplaneMode`. This is for
+ // avoiding the race conditions described in b/340744397
+ && (!isAirplaneModeOn() || enabledInAirplaneMode)
&& mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
}
@@ -710,6 +804,7 @@
if (mNetworkToInterface.containsKey(mUpstreamNetwork)) {
enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork));
}
+ mNsdPublisher.setNetworkForHostResolution(mUpstreamNetwork);
}
}
}
@@ -820,7 +915,6 @@
networkName,
supportedChannelMask,
preferredChannelMask,
- Instant.now(),
new Random(),
new SecureRandom());
@@ -838,9 +932,18 @@
String networkName,
int supportedChannelMask,
int preferredChannelMask,
- Instant now,
Random random,
SecureRandom secureRandom) {
+ boolean authoritative = false;
+ Instant now = Instant.now();
+ try {
+ Clock clock = SystemClock.currentNetworkTimeClock();
+ now = clock.instant();
+ authoritative = true;
+ } catch (DateTimeException e) {
+ Log.w(TAG, "Failed to get authoritative time", e);
+ }
+
int panId = random.nextInt(/* bound= */ 0xffff);
final byte[] meshLocalPrefix = newRandomBytes(random, LENGTH_MESH_LOCAL_PREFIX_BITS / 8);
meshLocalPrefix[0] = MESH_LOCAL_PREFIX_FIRST_BYTE;
@@ -852,9 +955,7 @@
final byte[] securityFlags = new byte[] {(byte) 0xff, (byte) 0xf8};
return new ActiveOperationalDataset.Builder()
- .setActiveTimestamp(
- new OperationalDatasetTimestamp(
- now.getEpochSecond() & 0xffffffffffffL, 0, false))
+ .setActiveTimestamp(OperationalDatasetTimestamp.fromInstant(now, authoritative))
.setExtendedPanId(newRandomBytes(random, LENGTH_EXTENDED_PAN_ID))
.setPanId(panId)
.setNetworkName(networkName)
@@ -960,7 +1061,11 @@
private void checkOnHandlerThread() {
if (Looper.myLooper() != mHandler.getLooper()) {
- Log.wtf(TAG, "Must be on the handler thread!");
+ throw new IllegalStateException(
+ "Not running on ThreadNetworkControllerService thread ("
+ + mHandler.getLooper()
+ + ") : "
+ + Looper.myLooper());
}
}
@@ -1058,7 +1163,7 @@
}
@Override
- public void leave(@NonNull IOperationReceiver receiver) throws RemoteException {
+ public void leave(@NonNull IOperationReceiver receiver) {
enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
mHandler.post(() -> leaveInternal(new OperationReceiverWrapper(receiver)));
@@ -1368,14 +1473,6 @@
}
}
- private void notifyThreadEnabledUpdated(IStateCallback callback, int enabledState) {
- try {
- callback.onThreadEnableStateChanged(enabledState);
- } catch (RemoteException ignored) {
- // do nothing if the client is dead
- }
- }
-
public void unregisterStateCallback(IStateCallback callback) {
checkOnHandlerThread();
if (!mStateCallbacks.containsKey(callback)) {
@@ -1442,15 +1539,19 @@
}
}
- @Override
- public void onThreadEnabledChanged(int state) {
- mHandler.post(() -> onThreadEnabledChangedInternal(state));
- }
-
- private void onThreadEnabledChangedInternal(int state) {
+ private void onThreadEnabledChanged(int state, long listenerId) {
checkOnHandlerThread();
- for (IStateCallback callback : mStateCallbacks.keySet()) {
- notifyThreadEnabledUpdated(callback, otStateToAndroidState(state));
+ boolean stateChanged = (mState == null || mState.threadEnabled != state);
+
+ for (var callbackEntry : mStateCallbacks.entrySet()) {
+ if (!stateChanged && callbackEntry.getValue().id != listenerId) {
+ continue;
+ }
+ try {
+ callbackEntry.getKey().onThreadEnableStateChanged(otStateToAndroidState(state));
+ } catch (RemoteException ignored) {
+ // do nothing if the client is dead
+ }
}
}
@@ -1477,6 +1578,7 @@
onInterfaceStateChanged(newState.isInterfaceUp);
onDeviceRoleChanged(newState.deviceRole, listenerId);
onPartitionIdChanged(newState.partitionId, listenerId);
+ onThreadEnabledChanged(newState.threadEnabled, listenerId);
mState = newState;
ActiveOperationalDataset newActiveDataset;
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkService.java b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
index 30c67ca..4c22278 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
@@ -72,7 +72,10 @@
// PHASE_ACTIVITY_MANAGER_READY and PHASE_THIRD_PARTY_APPS_CAN_START
mCountryCode.initialize();
mShellCommand =
- new ThreadNetworkShellCommand(requireNonNull(mControllerService), mCountryCode);
+ new ThreadNetworkShellCommand(
+ mContext,
+ requireNonNull(mControllerService),
+ requireNonNull(mCountryCode));
}
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java b/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
index c6a1618..54155ee 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
@@ -16,50 +16,57 @@
package com.android.server.thread;
-import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
+import android.net.thread.ActiveOperationalDataset;
import android.net.thread.IOperationReceiver;
+import android.net.thread.OperationalDatasetTimestamp;
+import android.net.thread.PendingOperationalDataset;
import android.net.thread.ThreadNetworkException;
-import android.os.Binder;
-import android.os.Process;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.net.module.util.HexDump;
import java.io.PrintWriter;
import java.time.Duration;
-import java.util.List;
+import java.time.Instant;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
- * Interprets and executes 'adb shell cmd thread_network [args]'.
+ * Interprets and executes 'adb shell cmd thread_network <subcommand>'.
+ *
+ * <p>Subcommands which don't have an equivalent Java API now require the
+ * "android.permission.THREAD_NETWORK_TESTING" permission. For a specific subcommand, it also
+ * requires the same permissions of the equivalent Java / AIDL API.
*
* <p>To add new commands: - onCommand: Add a case "<command>" execute. Return a 0 if command
* executed successfully. - onHelp: add a description string.
- *
- * <p>Permissions: currently root permission is required for some commands. Others will enforce the
- * corresponding API permissions.
*/
-public class ThreadNetworkShellCommand extends BasicShellCommandHandler {
+public final class ThreadNetworkShellCommand extends BasicShellCommandHandler {
private static final Duration SET_ENABLED_TIMEOUT = Duration.ofSeconds(2);
+ private static final Duration LEAVE_TIMEOUT = Duration.ofSeconds(2);
+ private static final Duration MIGRATE_TIMEOUT = Duration.ofSeconds(2);
private static final Duration FORCE_STOP_TIMEOUT = Duration.ofSeconds(1);
+ private static final String PERMISSION_THREAD_NETWORK_TESTING =
+ "android.permission.THREAD_NETWORK_TESTING";
- // These don't require root access.
- private static final List<String> NON_PRIVILEGED_COMMANDS =
- List.of("help", "get-country-code", "enable", "disable");
+ private final Context mContext;
+ private final ThreadNetworkControllerService mControllerService;
+ private final ThreadNetworkCountryCode mCountryCode;
- @NonNull private final ThreadNetworkControllerService mControllerService;
- @NonNull private final ThreadNetworkCountryCode mCountryCode;
@Nullable private PrintWriter mOutputWriter;
@Nullable private PrintWriter mErrorWriter;
- ThreadNetworkShellCommand(
- @NonNull ThreadNetworkControllerService controllerService,
- @NonNull ThreadNetworkCountryCode countryCode) {
+ public ThreadNetworkShellCommand(
+ Context context,
+ ThreadNetworkControllerService controllerService,
+ ThreadNetworkCountryCode countryCode) {
+ mContext = context;
mControllerService = controllerService;
mCountryCode = countryCode;
}
@@ -79,79 +86,120 @@
}
@Override
+ public void onHelp() {
+ final PrintWriter pw = getOutputWriter();
+ pw.println("Thread network commands:");
+ pw.println(" help or -h");
+ pw.println(" Print this help text.");
+ pw.println(" enable");
+ pw.println(" Enables Thread radio");
+ pw.println(" disable");
+ pw.println(" Disables Thread radio");
+ pw.println(" join <active-dataset-tlvs>");
+ pw.println(" Joins a network of the given dataset");
+ pw.println(" migrate <active-dataset-tlvs> <delay-seconds>");
+ pw.println(" Migrate to the given network by a specific delay");
+ pw.println(" leave");
+ pw.println(" Leave the current network and erase datasets");
+ pw.println(" force-stop-ot-daemon enabled | disabled ");
+ pw.println(" force stop ot-daemon service");
+ pw.println(" get-country-code");
+ pw.println(" Gets country code as a two-letter string");
+ pw.println(" force-country-code enabled <two-letter code> | disabled ");
+ pw.println(" Sets country code to <two-letter code> or left for normal value");
+ }
+
+ @Override
public int onCommand(String cmd) {
- // Treat no command as help command.
+ // Treat no command as the "help" command
if (TextUtils.isEmpty(cmd)) {
cmd = "help";
}
- final PrintWriter pw = getOutputWriter();
- final PrintWriter perr = getErrorWriter();
-
- // Explicit exclusion from root permission
- if (!NON_PRIVILEGED_COMMANDS.contains(cmd)) {
- final int uid = Binder.getCallingUid();
-
- if (uid != Process.ROOT_UID) {
- perr.println(
- "Uid "
- + uid
- + " does not have access to "
- + cmd
- + " thread command "
- + "(or such command doesn't exist)");
- return -1;
- }
- }
-
switch (cmd) {
case "enable":
return setThreadEnabled(true);
case "disable":
return setThreadEnabled(false);
+ case "join":
+ return join();
+ case "leave":
+ return leave();
+ case "migrate":
+ return migrate();
case "force-stop-ot-daemon":
return forceStopOtDaemon();
case "force-country-code":
- boolean enabled;
- try {
- enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
- } catch (IllegalArgumentException e) {
- perr.println("Invalid argument: " + e.getMessage());
- return -1;
- }
-
- if (enabled) {
- String countryCode = getNextArgRequired();
- if (!ThreadNetworkCountryCode.isValidCountryCode(countryCode)) {
- perr.println(
- "Invalid argument: Country code must be a 2-Character"
- + " string. But got country code "
- + countryCode
- + " instead");
- return -1;
- }
- mCountryCode.setOverrideCountryCode(countryCode);
- pw.println("Set Thread country code: " + countryCode);
-
- } else {
- mCountryCode.clearOverrideCountryCode();
- }
- return 0;
+ return forceCountryCode();
case "get-country-code":
- pw.println("Thread country code = " + mCountryCode.getCountryCode());
- return 0;
+ return getCountryCode();
default:
return handleDefaultCommands(cmd);
}
}
+ private void ensureTestingPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ PERMISSION_THREAD_NETWORK_TESTING,
+ "Permission " + PERMISSION_THREAD_NETWORK_TESTING + " is missing!");
+ }
+
private int setThreadEnabled(boolean enabled) {
CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
mControllerService.setEnabled(enabled, newOperationReceiver(setEnabledFuture));
- return waitForFuture(setEnabledFuture, FORCE_STOP_TIMEOUT, getErrorWriter());
+ return waitForFuture(setEnabledFuture, SET_ENABLED_TIMEOUT, getErrorWriter());
+ }
+
+ private int join() {
+ byte[] datasetTlvs = HexDump.hexStringToByteArray(getNextArgRequired());
+ ActiveOperationalDataset dataset;
+ try {
+ dataset = ActiveOperationalDataset.fromThreadTlvs(datasetTlvs);
+ } catch (IllegalArgumentException e) {
+ getErrorWriter().println("Invalid dataset argument: " + e.getMessage());
+ return -1;
+ }
+ // Do not wait for join to complete because this can take 8 to 30 seconds
+ mControllerService.join(dataset, new IOperationReceiver.Default());
+ return 0;
+ }
+
+ private int leave() {
+ CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
+ mControllerService.leave(newOperationReceiver(leaveFuture));
+ return waitForFuture(leaveFuture, LEAVE_TIMEOUT, getErrorWriter());
+ }
+
+ private int migrate() {
+ byte[] datasetTlvs = HexDump.hexStringToByteArray(getNextArgRequired());
+ ActiveOperationalDataset dataset;
+ try {
+ dataset = ActiveOperationalDataset.fromThreadTlvs(datasetTlvs);
+ } catch (IllegalArgumentException e) {
+ getErrorWriter().println("Invalid dataset argument: " + e.getMessage());
+ return -1;
+ }
+
+ int delaySeconds;
+ try {
+ delaySeconds = Integer.parseInt(getNextArgRequired());
+ } catch (NumberFormatException e) {
+ getErrorWriter().println("Invalid delay argument: " + e.getMessage());
+ return -1;
+ }
+
+ PendingOperationalDataset pendingDataset =
+ new PendingOperationalDataset(
+ dataset,
+ OperationalDatasetTimestamp.fromInstant(Instant.now()),
+ Duration.ofSeconds(delaySeconds));
+ CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
+ mControllerService.scheduleMigration(pendingDataset, newOperationReceiver(migrateFuture));
+ return waitForFuture(migrateFuture, MIGRATE_TIMEOUT, getErrorWriter());
}
private int forceStopOtDaemon() {
+ ensureTestingPermission();
final PrintWriter errorWriter = getErrorWriter();
boolean enabled;
try {
@@ -166,6 +214,40 @@
return waitForFuture(forceStopFuture, FORCE_STOP_TIMEOUT, getErrorWriter());
}
+ private int forceCountryCode() {
+ ensureTestingPermission();
+ final PrintWriter perr = getErrorWriter();
+ boolean enabled;
+ try {
+ enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
+ } catch (IllegalArgumentException e) {
+ perr.println("Invalid argument: " + e.getMessage());
+ return -1;
+ }
+
+ if (enabled) {
+ String countryCode = getNextArgRequired();
+ if (!ThreadNetworkCountryCode.isValidCountryCode(countryCode)) {
+ perr.println(
+ "Invalid argument: Country code must be a 2-letter"
+ + " string. But got country code "
+ + countryCode
+ + " instead");
+ return -1;
+ }
+ mCountryCode.setOverrideCountryCode(countryCode);
+ } else {
+ mCountryCode.clearOverrideCountryCode();
+ }
+ return 0;
+ }
+
+ private int getCountryCode() {
+ ensureTestingPermission();
+ getOutputWriter().println("Thread country code = " + mCountryCode.getCountryCode());
+ return 0;
+ }
+
private static IOperationReceiver newOperationReceiver(CompletableFuture<Void> future) {
return new IOperationReceiver.Stub() {
@Override
@@ -224,33 +306,4 @@
String nextArg = getNextArgRequired();
return argTrueOrFalse(nextArg, trueString, falseString);
}
-
- private void onHelpNonPrivileged(PrintWriter pw) {
- pw.println(" enable");
- pw.println(" Enables Thread radio");
- pw.println(" disable");
- pw.println(" Disables Thread radio");
- pw.println(" get-country-code");
- pw.println(" Gets country code as a two-letter string");
- }
-
- private void onHelpPrivileged(PrintWriter pw) {
- pw.println(" force-country-code enabled <two-letter code> | disabled ");
- pw.println(" Sets country code to <two-letter code> or left for normal value");
- pw.println(" force-stop-ot-daemon enabled | disabled ");
- pw.println(" force stop ot-daemon service");
- }
-
- @Override
- public void onHelp() {
- final PrintWriter pw = getOutputWriter();
- pw.println("Thread network commands:");
- pw.println(" help or -h");
- pw.println(" Print this help text.");
- onHelpNonPrivileged(pw);
- if (Binder.getCallingUid() == Process.ROOT_UID) {
- onHelpPrivileged(pw);
- }
- pw.println();
- }
}
diff --git a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
index f18aac9..747cc96 100644
--- a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
+++ b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
@@ -18,9 +18,11 @@
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ApexEnvironment;
import android.content.Context;
+import android.net.thread.ThreadConfiguration;
import android.os.PersistableBundle;
import android.util.AtomicFile;
import android.util.Log;
@@ -74,6 +76,16 @@
/** Stores the Thread country code, null if no country code is stored. */
public static final Key<String> THREAD_COUNTRY_CODE = new Key<>("thread_country_code", null);
+ /** Stores the Thread NAT64 feature toggle state, true for enabled and false for disabled. */
+ private static final Key<Boolean> CONFIG_NAT64_ENABLED =
+ new Key<>("config_nat64_enabled", false);
+
+ /**
+ * Stores the Thread DHCPv6-PD feature toggle state, true for enabled and false for disabled.
+ */
+ private static final Key<Boolean> CONFIG_DHCP6_PD_ENABLED =
+ new Key<>("config_dhcp6_pd_enabled", false);
+
/******** Thread persistent setting keys ***************/
@GuardedBy("mLock")
@@ -175,6 +187,30 @@
}
/**
+ * Store a {@link ThreadConfiguration} to the persistent settings.
+ *
+ * @param configuration {@link ThreadConfiguration} to be stored.
+ * @return {@code true} if the configuration was changed, {@code false} otherwise.
+ */
+ public boolean putConfiguration(@NonNull ThreadConfiguration configuration) {
+ if (getConfiguration().equals(configuration)) {
+ return false;
+ }
+ putObject(CONFIG_NAT64_ENABLED.key, configuration.isNat64Enabled());
+ putObject(CONFIG_DHCP6_PD_ENABLED.key, configuration.isDhcp6PdEnabled());
+ writeToStoreFile();
+ return true;
+ }
+
+ /** Retrieve the {@link ThreadConfiguration} from the persistent settings. */
+ public ThreadConfiguration getConfiguration() {
+ return new ThreadConfiguration.Builder()
+ .setNat64Enabled(get(CONFIG_NAT64_ENABLED))
+ .setDhcp6PdEnabled(get(CONFIG_DHCP6_PD_ENABLED))
+ .build();
+ }
+
+ /**
* Base class to store string key and its default value.
*
* @param <T> Type of the value.
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index b76c1e9..976f93d 100644
--- a/thread/service/java/com/android/server/thread/TunInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java
@@ -16,30 +16,40 @@
package com.android.server.thread;
+import static android.system.OsConstants.AF_INET6;
import static android.system.OsConstants.EADDRINUSE;
+import static android.system.OsConstants.IFF_MULTICAST;
+import static android.system.OsConstants.IFF_NOARP;
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_AF_SPEC;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_INET6_ADDR_GEN_MODE;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IN6_ADDR_GEN_MODE_NONE;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
import android.annotation.Nullable;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.RouteInfo;
-import android.net.util.SocketUtils;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
-import android.system.OsConstants;
import android.util.Log;
+import com.android.net.module.util.HexDump;
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
import com.android.net.module.util.netlink.NetlinkUtils;
-import com.android.net.module.util.netlink.RtNetlinkAddressMessage;
+import com.android.net.module.util.netlink.StructIfinfoMsg;
+import com.android.net.module.util.netlink.StructNlAttr;
+import com.android.net.module.util.netlink.StructNlMsgHdr;
import com.android.server.thread.openthread.Ipv6AddressInfo;
import com.android.server.thread.openthread.OnMeshPrefixConfig;
-import java.io.FileDescriptor;
import java.io.IOException;
-import java.io.InterruptedIOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -47,12 +57,15 @@
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
/** Controller for virtual/tunnel network interfaces. */
public class TunInterfaceController {
private static final String TAG = "TunIfController";
+ private static final boolean DBG = false;
private static final long INFINITE_LIFETIME = 0xffffffffL;
static final int MTU = 1280;
@@ -62,14 +75,13 @@
private final String mIfName;
private final LinkProperties mLinkProperties = new LinkProperties();
- private ParcelFileDescriptor mParcelTunFd;
- private FileDescriptor mNetlinkSocket;
- private static int sNetlinkSeqNo = 0;
private final MulticastSocket mMulticastSocket; // For join group and leave group
- private NetworkInterface mNetworkInterface;
private final List<InetAddress> mMulticastAddresses = new ArrayList<>();
private final List<RouteInfo> mNetDataPrefixes = new ArrayList<>();
+ private ParcelFileDescriptor mParcelTunFd;
+ private NetworkInterface mNetworkInterface;
+
/** Creates a new {@link TunInterfaceController} instance for given interface. */
public TunInterfaceController(String interfaceName) {
mIfName = interfaceName;
@@ -91,26 +103,21 @@
public void createTunInterface() throws IOException {
mParcelTunFd = ParcelFileDescriptor.adoptFd(nativeCreateTunInterface(mIfName, MTU));
try {
- mNetlinkSocket = NetlinkUtils.netlinkSocketForProto(OsConstants.NETLINK_ROUTE);
- } catch (ErrnoException e) {
- throw new IOException("Failed to create netlink socket", e);
- }
- try {
mNetworkInterface = NetworkInterface.getByName(mIfName);
} catch (SocketException e) {
throw new IOException("Failed to get NetworkInterface", e);
}
+
+ setAddrGenModeToNone();
}
public void destroyTunInterface() {
try {
mParcelTunFd.close();
- SocketUtils.closeSocket(mNetlinkSocket);
} catch (IOException e) {
// Should never fail
}
mParcelTunFd = null;
- mNetlinkSocket = null;
mNetworkInterface = null;
}
@@ -142,14 +149,14 @@
public void addAddress(LinkAddress address) {
Log.d(TAG, "Adding address " + address + " with flags: " + address.getFlags());
- long validLifetimeSeconds;
long preferredLifetimeSeconds;
+ long validLifetimeSeconds;
if (address.getDeprecationTime() == LinkAddress.LIFETIME_PERMANENT
|| address.getDeprecationTime() == LinkAddress.LIFETIME_UNKNOWN) {
- validLifetimeSeconds = INFINITE_LIFETIME;
+ preferredLifetimeSeconds = INFINITE_LIFETIME;
} else {
- validLifetimeSeconds =
+ preferredLifetimeSeconds =
Math.max(
(address.getDeprecationTime() - SystemClock.elapsedRealtime()) / 1000L,
0L);
@@ -157,28 +164,23 @@
if (address.getExpirationTime() == LinkAddress.LIFETIME_PERMANENT
|| address.getExpirationTime() == LinkAddress.LIFETIME_UNKNOWN) {
- preferredLifetimeSeconds = INFINITE_LIFETIME;
+ validLifetimeSeconds = INFINITE_LIFETIME;
} else {
- preferredLifetimeSeconds =
+ validLifetimeSeconds =
Math.max(
(address.getExpirationTime() - SystemClock.elapsedRealtime()) / 1000L,
0L);
}
- byte[] message =
- RtNetlinkAddressMessage.newRtmNewAddressMessage(
- sNetlinkSeqNo++,
- address.getAddress(),
- (short) address.getPrefixLength(),
- address.getFlags(),
- (byte) address.getScope(),
- Os.if_nametoindex(mIfName),
- validLifetimeSeconds,
- preferredLifetimeSeconds);
- try {
- Os.write(mNetlinkSocket, message, 0, message.length);
- } catch (ErrnoException | InterruptedIOException e) {
- Log.e(TAG, "Failed to add address " + address, e);
+ if (!NetlinkUtils.sendRtmNewAddressRequest(
+ Os.if_nametoindex(mIfName),
+ address.getAddress(),
+ (short) address.getPrefixLength(),
+ address.getFlags(),
+ (byte) address.getScope(),
+ preferredLifetimeSeconds,
+ validLifetimeSeconds)) {
+ Log.w(TAG, "Failed to add address " + address.getAddress().getHostAddress());
return;
}
mLinkProperties.addLinkAddress(address);
@@ -188,22 +190,17 @@
/** Removes an address from the interface. */
public void removeAddress(LinkAddress address) {
Log.d(TAG, "Removing address " + address);
- byte[] message =
- RtNetlinkAddressMessage.newRtmDelAddressMessage(
- sNetlinkSeqNo++,
- address.getAddress(),
- (short) address.getPrefixLength(),
- Os.if_nametoindex(mIfName));
// Intentionally update the mLinkProperties before send netlink message because the
// address is already removed from ot-daemon and apps can't reach to the address even
// when the netlink request below fails
mLinkProperties.removeLinkAddress(address);
mLinkProperties.removeRoute(getRouteForAddress(address));
- try {
- Os.write(mNetlinkSocket, message, 0, message.length);
- } catch (ErrnoException | InterruptedIOException e) {
- Log.e(TAG, "Failed to remove address " + address, e);
+ if (!NetlinkUtils.sendRtmDelAddressRequest(
+ Os.if_nametoindex(mIfName),
+ (Inet6Address) address.getAddress(),
+ (short) address.getPrefixLength())) {
+ Log.w(TAG, "Failed to remove address " + address.getAddress().getHostAddress());
}
}
@@ -366,4 +363,66 @@
Log.e(TAG, "failed to leave group " + address.getHostAddress(), e);
}
}
+
+ /**
+ * Sets the address generation mode to {@code IN6_ADDR_GEN_MODE_NONE}.
+ *
+ * <p>So that the "thread-wpan" interface has only one IPv6 link local address which is
+ * generated by OpenThread.
+ */
+ private void setAddrGenModeToNone() {
+ StructNlMsgHdr header = new StructNlMsgHdr();
+ header.nlmsg_type = RTM_NEWLINK;
+ header.nlmsg_pid = 0;
+ header.nlmsg_seq = 0;
+ header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+
+ StructIfinfoMsg ifInfo =
+ new StructIfinfoMsg(
+ (short) 0 /* family */,
+ 0 /* type */,
+ Os.if_nametoindex(mIfName),
+ (IFF_MULTICAST | IFF_NOARP) /* flags */,
+ 0xffffffff /* change */);
+
+ // Nested attributes
+ // IFLA_AF_SPEC
+ // AF_INET6
+ // IFLA_INET6_ADDR_GEN_MODE
+ StructNlAttr addrGenMode =
+ new StructNlAttr(IFLA_INET6_ADDR_GEN_MODE, (byte) IN6_ADDR_GEN_MODE_NONE);
+ StructNlAttr afInet6 = new StructNlAttr((short) AF_INET6, addrGenMode);
+ StructNlAttr afSpec = new StructNlAttr(IFLA_AF_SPEC, afInet6);
+
+ final int msgLength =
+ StructNlMsgHdr.STRUCT_SIZE
+ + StructIfinfoMsg.STRUCT_SIZE
+ + afSpec.getAlignedLength();
+ byte[] msg = new byte[msgLength];
+ ByteBuffer buf = ByteBuffer.wrap(msg);
+ buf.order(ByteOrder.nativeOrder());
+
+ header.nlmsg_len = msgLength;
+ header.pack(buf);
+ ifInfo.pack(buf);
+ afSpec.pack(buf);
+
+ if (buf.position() != msgLength) {
+ throw new AssertionError(
+ String.format(
+ "Unexpected netlink message size (actual = %d, expected = %d)",
+ buf.position(), msgLength));
+ }
+
+ if (DBG) {
+ Log.d(TAG, "ADDR_GEN_MODE message is:");
+ Log.d(TAG, HexDump.dumpHexString(msg));
+ }
+
+ try {
+ NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to set ADDR_GEN_MODE to NONE", e);
+ }
+ }
}
diff --git a/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp b/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
index 5d24eab..1f260f2 100644
--- a/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
+++ b/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
@@ -42,15 +42,8 @@
namespace android {
static jint
-com_android_server_thread_InfraInterfaceController_createIcmp6Socket(JNIEnv *env, jobject clazz,
- jstring interfaceName) {
- ScopedUtfChars ifName(env, interfaceName);
-
- struct icmp6_filter filter;
- constexpr int kEnable = 1;
- constexpr int kIpv6ChecksumOffset = 2;
- constexpr int kHopLimit = 255;
-
+com_android_server_thread_InfraInterfaceController_createFilteredIcmp6Socket(JNIEnv *env,
+ jobject clazz) {
// Initializes the ICMPv6 socket.
int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if (sock == -1) {
@@ -59,6 +52,7 @@
return -1;
}
+ struct icmp6_filter filter;
// Only accept Router Advertisements, Router Solicitations and Neighbor
// Advertisements.
ICMP6_FILTER_SETBLOCKALL(&filter);
@@ -73,53 +67,6 @@
return -1;
}
- // We want a source address and interface index.
-
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &kEnable, sizeof(kEnable)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_RECVPKTINFO (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &kIpv6ChecksumOffset,
- sizeof(kIpv6ChecksumOffset)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_CHECKSUM (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- // We need to be able to reject RAs arriving from off-link.
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &kEnable, sizeof(kEnable)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_RECVHOPLIMIT (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &kHopLimit, sizeof(kHopLimit)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_UNICAST_HOPS (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &kHopLimit, sizeof(kHopLimit)) != 0) {
- jniThrowExceptionFmt(env, "java/io/IOException",
- "failed to create the setsockopt IPV6_MULTICAST_HOPS (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
- if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifName.c_str(), strlen(ifName.c_str()))) {
- jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt SO_BINDTODEVICE (%s)",
- strerror(errno));
- close(sock);
- return -1;
- }
-
return sock;
}
@@ -129,8 +76,8 @@
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
- {"nativeCreateIcmp6Socket", "(Ljava/lang/String;)I",
- (void *)com_android_server_thread_InfraInterfaceController_createIcmp6Socket},
+ {"nativeCreateFilteredIcmp6Socket", "()I",
+ (void *)com_android_server_thread_InfraInterfaceController_createFilteredIcmp6Socket},
};
int register_com_android_server_thread_InfraInterfaceController(JNIEnv *env) {
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index 8cdf38d..c1cf0a0 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -21,6 +21,7 @@
android_test {
name: "CtsThreadNetworkTestCases",
+ defaults: ["cts_defaults"],
min_sdk_version: "33",
sdk_version: "test_current",
manifest: "AndroidManifest.xml",
diff --git a/thread/tests/cts/AndroidTest.xml b/thread/tests/cts/AndroidTest.xml
index ffc181c..6eda1e9 100644
--- a/thread/tests/cts/AndroidTest.xml
+++ b/thread/tests/cts/AndroidTest.xml
@@ -38,6 +38,11 @@
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
<!-- Install test -->
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="CtsThreadNetworkTestCases.apk" />
diff --git a/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java b/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java
index 0e76930..996d22d 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java
@@ -30,6 +30,8 @@
import android.net.thread.ActiveOperationalDataset.Builder;
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
import android.net.thread.OperationalDatasetTimestamp;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.util.SparseArray;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,6 +40,7 @@
import com.google.common.primitives.Bytes;
import com.google.common.testing.EqualsTester;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,6 +49,7 @@
/** CTS tests for {@link ActiveOperationalDataset}. */
@SmallTest
+@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public final class ActiveOperationalDatasetTest {
private static final int TYPE_ACTIVE_TIMESTAMP = 14;
@@ -81,6 +85,8 @@
private static final ActiveOperationalDataset DEFAULT_DATASET =
ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET_TLVS);
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
private static byte[] removeTlv(byte[] dataset, int type) {
ByteArrayOutputStream os = new ByteArrayOutputStream(dataset.length);
int i = 0;
diff --git a/thread/tests/cts/src/android/net/thread/cts/OperationalDatasetTimestampTest.java b/thread/tests/cts/src/android/net/thread/cts/OperationalDatasetTimestampTest.java
index 9be3d56..4d7c7f1 100644
--- a/thread/tests/cts/src/android/net/thread/cts/OperationalDatasetTimestampTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/OperationalDatasetTimestampTest.java
@@ -21,12 +21,15 @@
import static org.junit.Assert.assertThrows;
import android.net.thread.OperationalDatasetTimestamp;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.common.testing.EqualsTester;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -34,8 +37,11 @@
/** Tests for {@link OperationalDatasetTimestamp}. */
@SmallTest
+@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public final class OperationalDatasetTimestampTest {
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
@Test
public void fromInstant_tooLargeInstant_throwsIllegalArgument() {
assertThrows(
diff --git a/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java b/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java
index 0bb18ce..76be054 100644
--- a/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java
@@ -28,6 +28,8 @@
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
import android.net.thread.OperationalDatasetTimestamp;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.util.SparseArray;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -36,6 +38,7 @@
import com.google.common.primitives.Bytes;
import com.google.common.testing.EqualsTester;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,8 +47,11 @@
/** Tests for {@link PendingOperationalDataset}. */
@SmallTest
+@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public final class PendingOperationalDatasetTest {
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
private static ActiveOperationalDataset createActiveDataset() throws Exception {
SparseArray<byte[]> channelMask = new SparseArray<>(1);
channelMask.put(0, new byte[] {0x00, 0x1f, (byte) 0xff, (byte) 0xe0});
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 dea4279..41f34ff 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -43,7 +43,6 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
@@ -174,6 +173,17 @@
}
@Test
+ public void subscribeThreadEnableState_getActiveDataset_onThreadEnableStateChangedNotCalled()
+ throws Exception {
+ EnabledStateListener listener = new EnabledStateListener(mController);
+ listener.expectThreadEnabledState(STATE_ENABLED);
+
+ getActiveOperationalDataset(mController);
+
+ listener.expectCallbackNotCalled();
+ }
+
+ @Test
public void registerStateCallback_returnsUpdatedEnabledStates() throws Exception {
CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
@@ -849,6 +859,7 @@
assertThat(txtMap.get("rv")).isNotNull();
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
+ assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test
@@ -875,6 +886,7 @@
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
assertThat(txtMap.get("id").length).isEqualTo(16);
+ assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test
@@ -1016,7 +1028,11 @@
}
public void expectThreadEnabledState(int enabled) {
- assertNotNull(mReadHead.poll(ENABLED_TIMEOUT_MILLIS, e -> (e == enabled)));
+ assertThat(mReadHead.poll(ENABLED_TIMEOUT_MILLIS, e -> (e == enabled))).isNotNull();
+ }
+
+ public void expectCallbackNotCalled() {
+ assertThat(mReadHead.poll(CALLBACK_TIMEOUT_MILLIS, e -> true)).isNull();
}
public void unregisterStateCallback() {
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
index 7d9ae81..4de2e13 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
@@ -25,17 +25,23 @@
import static org.junit.Assert.assertThrows;
import android.net.thread.ThreadNetworkException;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** CTS tests for {@link ThreadNetworkException}. */
@SmallTest
+@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public final class ThreadNetworkExceptionTest {
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
@Test
public void constructor_validValues_valuesAreConnectlySet() throws Exception {
ThreadNetworkException errorThreadDisabled =
diff --git a/thread/tests/integration/AndroidTest.xml b/thread/tests/integration/AndroidTest.xml
index 152c1c3..8f98941 100644
--- a/thread/tests/integration/AndroidTest.xml
+++ b/thread/tests/integration/AndroidTest.xml
@@ -31,6 +31,11 @@
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<!-- Install test -->
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index 91ca23e..61b6eac 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -176,7 +176,7 @@
// TODO (b/323300829): add test for removing an OT address
@Test
- public void tunInterface_joinedNetwork_otAddressesAddedToTunInterface() throws Exception {
+ public void tunInterface_joinedNetwork_otAndTunAddressesMatch() throws Exception {
mController.joinAndWait(DEFAULT_DATASET);
List<Inet6Address> otAddresses = mOtCtl.getAddresses();
@@ -185,9 +185,12 @@
// that we can write assertThat() in the Predicate
waitFor(
() -> {
- String ifconfig = runShellCommand("ifconfig thread-wpan");
- return otAddresses.stream()
- .allMatch(addr -> ifconfig.contains(addr.getHostAddress()));
+ List<Inet6Address> tunAddresses =
+ getIpv6LinkAddresses("thread-wpan").stream()
+ .map(linkAddr -> (Inet6Address) linkAddr.getAddress())
+ .toList();
+ return otAddresses.containsAll(tunAddresses)
+ && tunAddresses.containsAll(otAddresses);
},
TUN_ADDR_UPDATE_TIMEOUT);
}
diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
index 46cf562..c0a8eea 100644
--- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -20,10 +20,14 @@
import static com.google.common.io.BaseEncoding.base16;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.nsd.NsdServiceInfo;
import android.net.thread.ActiveOperationalDataset;
+import android.os.Handler;
+import android.os.HandlerThread;
import com.google.errorprone.annotations.FormatMethod;
@@ -39,6 +43,8 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -60,10 +66,13 @@
private static final float PING_TIMEOUT_0_1_SECOND = 0.1f;
// 1 second timeout should be used when response is expected.
private static final float PING_TIMEOUT_1_SECOND = 1f;
+ private static final int READ_LINE_TIMEOUT_SECONDS = 5;
private final Process mProcess;
private final BufferedReader mReader;
private final BufferedWriter mWriter;
+ private final HandlerThread mReaderHandlerThread;
+ private final Handler mReaderHandler;
private ActiveOperationalDataset mActiveOperationalDataset;
@@ -87,11 +96,15 @@
}
mReader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
mWriter = new BufferedWriter(new OutputStreamWriter(mProcess.getOutputStream()));
+ mReaderHandlerThread = new HandlerThread("FullThreadDeviceReader");
+ mReaderHandlerThread.start();
+ mReaderHandler = new Handler(mReaderHandlerThread.getLooper());
mActiveOperationalDataset = null;
}
public void destroy() {
mProcess.destroy();
+ mReaderHandlerThread.quit();
}
/**
@@ -213,7 +226,7 @@
public String udpReceive() throws IOException {
Pattern pattern =
Pattern.compile("> (\\d+) bytes from ([\\da-f:]+) (\\d+) ([\\x00-\\x7F]+)");
- Matcher matcher = pattern.matcher(mReader.readLine());
+ Matcher matcher = pattern.matcher(readLine());
matcher.matches();
return matcher.group(4);
@@ -500,10 +513,27 @@
}
}
+ private String readLine() throws IOException {
+ final CompletableFuture<String> future = new CompletableFuture<>();
+ mReaderHandler.post(
+ () -> {
+ try {
+ future.complete(mReader.readLine());
+ } catch (IOException e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ return future.get(READ_LINE_TIMEOUT_SECONDS, SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ throw new IOException("Failed to read a line from ot-cli-ftd");
+ }
+ }
+
private List<String> readUntilDone() throws IOException {
ArrayList<String> result = new ArrayList<>();
String line;
- while ((line = mReader.readLine()) != null) {
+ while ((line = readLine()) != null) {
if (line.equals("Done")) {
break;
}
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
index 78f5770..ada46c8 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -301,7 +301,7 @@
return false;
}
- public static List<LinkAddress> getIpv6LinkAddresses(String interfaceName) throws IOException {
+ public static List<LinkAddress> getIpv6LinkAddresses(String interfaceName) {
List<LinkAddress> addresses = new ArrayList<>();
final String cmd = " ip -6 addr show dev " + interfaceName;
final String output = runShellCommandOrThrow(cmd);
diff --git a/thread/tests/multidevices/AndroidTest.xml b/thread/tests/multidevices/AndroidTest.xml
index a2ea9aa..8b2bed3 100644
--- a/thread/tests/multidevices/AndroidTest.xml
+++ b/thread/tests/multidevices/AndroidTest.xml
@@ -44,7 +44,7 @@
<test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest">
<!-- The mobly-par-file-name should match the module name -->
- <option name="mobly-par-file-name" value="ThreadMultiDeviceTestCases" />
+ <option name="mobly-par-file-name" value="ThreadNetworkMultiDeviceTests" />
<!-- Timeout limit in milliseconds for all test cases of the python binary -->
<option name="mobly-test-timeout" value="180000" />
</test>
diff --git a/thread/tests/unit/Android.bp b/thread/tests/unit/Android.bp
index 3365cd0..9404d1b 100644
--- a/thread/tests/unit/Android.bp
+++ b/thread/tests/unit/Android.bp
@@ -33,6 +33,7 @@
"mts-tethering",
],
static_libs: [
+ "androidx.test.rules",
"frameworks-base-testutils",
"framework-connectivity-pre-jarjar",
"framework-connectivity-t-pre-jarjar",
diff --git a/thread/tests/unit/AndroidTest.xml b/thread/tests/unit/AndroidTest.xml
index d16e423..58e9bdd 100644
--- a/thread/tests/unit/AndroidTest.xml
+++ b/thread/tests/unit/AndroidTest.xml
@@ -31,6 +31,11 @@
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="ThreadNetworkUnitTests.apk" />
<option name="check-min-sdk" value="true" />
diff --git a/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java b/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
index 2244a89..11c78e3 100644
--- a/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
+++ b/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
@@ -41,6 +41,19 @@
}
@Test
+ public void fromInstant_authoritativeIsSetAsSpecified() {
+ Instant instant = Instant.now();
+
+ OperationalDatasetTimestamp timestampAuthoritativeFalse =
+ OperationalDatasetTimestamp.fromInstant(instant, false);
+ OperationalDatasetTimestamp timestampAuthoritativeTrue =
+ OperationalDatasetTimestamp.fromInstant(instant, true);
+
+ assertThat(timestampAuthoritativeFalse.isAuthoritativeSource()).isEqualTo(false);
+ assertThat(timestampAuthoritativeTrue.isAuthoritativeSource()).isEqualTo(true);
+ }
+
+ @Test
public void fromTlvValue_goodValue_success() {
OperationalDatasetTimestamp timestamp =
OperationalDatasetTimestamp.fromTlvValue(base16().decode("FFEEDDCCBBAA9989"));
diff --git a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
index 8886c73..b32986d 100644
--- a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
@@ -16,56 +16,73 @@
package com.android.server.thread;
+import static android.net.DnsResolver.ERROR_SYSTEM;
import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
import static com.google.common.truth.Truth.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+import android.net.DnsResolver;
import android.net.InetAddresses;
+import android.net.Network;
import android.net.nsd.DiscoveryRequest;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.test.TestLooper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.INsdDiscoverServiceCallback;
+import com.android.server.thread.openthread.INsdResolveHostCallback;
import com.android.server.thread.openthread.INsdResolveServiceCallback;
import com.android.server.thread.openthread.INsdStatusReceiver;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.net.InetAddress;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
/** Unit tests for {@link NsdPublisher}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
public final class NsdPublisherTest {
+ private static final DnsTxtAttribute TEST_TXT_ENTRY_1 =
+ new DnsTxtAttribute("key1", new byte[] {0x01, 0x02});
+ private static final DnsTxtAttribute TEST_TXT_ENTRY_2 =
+ new DnsTxtAttribute("key2", new byte[] {0x03});
+
@Mock private NsdManager mMockNsdManager;
+ @Mock private DnsResolver mMockDnsResolver;
@Mock private INsdStatusReceiver mRegistrationReceiver;
@Mock private INsdStatusReceiver mUnregistrationReceiver;
@Mock private INsdDiscoverServiceCallback mDiscoverServiceCallback;
@Mock private INsdResolveServiceCallback mResolveServiceCallback;
+ @Mock private INsdResolveHostCallback mResolveHostCallback;
+ @Mock private Network mNetwork;
private TestLooper mTestLooper;
private NsdPublisher mNsdPublisher;
@@ -79,19 +96,15 @@
public void registerService_nsdManagerSucceeds_serviceRegistrationSucceeds() throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
-
mTestLooper.dispatchAll();
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
@@ -118,11 +131,10 @@
assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
- assertThat(actualServiceInfo.getAttributes().get("key1"))
- .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
- assertThat(actualServiceInfo.getAttributes().get("key2"))
- .isEqualTo(new byte[] {(byte) 0x03});
-
+ assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_1.name))
+ .isEqualTo(TEST_TXT_ENTRY_1.value);
+ assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_2.name))
+ .isEqualTo(TEST_TXT_ENTRY_2.value);
verify(mRegistrationReceiver, times(1)).onSuccess();
}
@@ -130,19 +142,15 @@
public void registerService_nsdManagerFails_serviceRegistrationFails() throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
-
mTestLooper.dispatchAll();
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
@@ -169,21 +177,16 @@
assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
- assertThat(actualServiceInfo.getAttributes().get("key1"))
- .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
- assertThat(actualServiceInfo.getAttributes().get("key2"))
- .isEqualTo(new byte[] {(byte) 0x03});
-
+ assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_1.name))
+ .isEqualTo(TEST_TXT_ENTRY_1.value);
+ assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_2.name))
+ .isEqualTo(TEST_TXT_ENTRY_2.value);
verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
}
@Test
public void registerService_nsdManagerThrows_serviceRegistrationFails() throws Exception {
prepareTest();
-
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
doThrow(new IllegalArgumentException("NsdManager fails"))
.when(mMockNsdManager)
.registerService(any(), anyInt(), any(Executor.class), any());
@@ -194,7 +197,7 @@
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
mTestLooper.dispatchAll();
@@ -207,16 +210,13 @@
throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
@@ -252,16 +252,13 @@
public void unregisterService_nsdManagerFails_serviceUnregistrationFails() throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
mNsdPublisher.registerService(
null,
"MyService",
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
@@ -579,8 +576,8 @@
List.of(
InetAddress.parseNumericAddress("2001::1"),
InetAddress.parseNumericAddress("2001::2")));
- serviceInfo.setAttribute("key1", new byte[] {(byte) 0x01, (byte) 0x02});
- serviceInfo.setAttribute("key2", new byte[] {(byte) 0x03});
+ serviceInfo.setAttribute(TEST_TXT_ENTRY_1.name, TEST_TXT_ENTRY_1.value);
+ serviceInfo.setAttribute(TEST_TXT_ENTRY_2.name, TEST_TXT_ENTRY_2.value);
serviceInfoCallbackArgumentCaptor.getValue().onServiceUpdated(serviceInfo);
mTestLooper.dispatchAll();
@@ -591,11 +588,8 @@
eq("_test._tcp"),
eq(12345),
eq(List.of("2001::1", "2001::2")),
- argThat(
- new TxtMatcher(
- List.of(
- makeTxtAttribute("key1", List.of(0x01, 0x02)),
- makeTxtAttribute("key2", List.of(0x03))))),
+ (List<DnsTxtAttribute>)
+ argThat(containsInAnyOrder(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2)),
anyInt());
}
@@ -637,12 +631,86 @@
}
@Test
- public void reset_unregisterAll() {
+ public void resolveHost_hostResolved() throws Exception {
prepareTest();
- DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
- DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+ mNsdPublisher.resolveHost("test", mResolveHostCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+ ArgumentCaptor<DnsResolver.Callback<List<InetAddress>>> resolveHostCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(DnsResolver.Callback.class);
+ verify(mMockDnsResolver, times(1))
+ .query(
+ eq(mNetwork),
+ eq("test.local"),
+ eq(DnsResolver.FLAG_NO_CACHE_LOOKUP),
+ any(Executor.class),
+ any(CancellationSignal.class),
+ resolveHostCallbackArgumentCaptor.capture());
+ resolveHostCallbackArgumentCaptor
+ .getValue()
+ .onAnswer(
+ List.of(
+ InetAddresses.parseNumericAddress("2001::1"),
+ InetAddresses.parseNumericAddress("2001::2")),
+ 0);
+ mTestLooper.dispatchAll();
+
+ verify(mResolveHostCallback, times(1))
+ .onHostResolved("test", List.of("2001::1", "2001::2"));
+ }
+
+ @Test
+ public void resolveHost_errorReported() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.resolveHost("test", mResolveHostCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<DnsResolver.Callback<List<InetAddress>>> resolveHostCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(DnsResolver.Callback.class);
+ verify(mMockDnsResolver, times(1))
+ .query(
+ eq(mNetwork),
+ eq("test.local"),
+ eq(DnsResolver.FLAG_NO_CACHE_LOOKUP),
+ any(Executor.class),
+ any(CancellationSignal.class),
+ resolveHostCallbackArgumentCaptor.capture());
+ resolveHostCallbackArgumentCaptor
+ .getValue()
+ .onError(new DnsResolver.DnsException(ERROR_SYSTEM, null /* cause */));
+ mTestLooper.dispatchAll();
+
+ verify(mResolveHostCallback, times(1)).onHostResolved("test", Collections.emptyList());
+ }
+
+ @Test
+ public void stopHostResolution() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.resolveHost("test", mResolveHostCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+ ArgumentCaptor<CancellationSignal> cancellationSignalArgumentCaptor =
+ ArgumentCaptor.forClass(CancellationSignal.class);
+ verify(mMockDnsResolver, times(1))
+ .query(
+ eq(mNetwork),
+ eq("test.local"),
+ eq(DnsResolver.FLAG_NO_CACHE_LOOKUP),
+ any(Executor.class),
+ cancellationSignalArgumentCaptor.capture(),
+ any(DnsResolver.Callback.class));
+
+ mNsdPublisher.stopHostResolution(10 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ assertThat(cancellationSignalArgumentCaptor.getValue().isCanceled()).isTrue();
+ }
+
+ @Test
+ public void reset_unregisterAll() {
+ prepareTest();
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
ArgumentCaptor.forClass(NsdServiceInfo.class);
ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
@@ -654,7 +722,7 @@
"_test._tcp",
List.of("_subtype1", "_subtype2"),
12345,
- List.of(txt1, txt2),
+ List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
mRegistrationReceiver,
16 /* listenerId */);
mTestLooper.dispatchAll();
@@ -728,19 +796,6 @@
verify(spyNsdPublisher, times(1)).reset();
}
- private static DnsTxtAttribute makeTxtAttribute(String name, List<Integer> value) {
- DnsTxtAttribute txtAttribute = new DnsTxtAttribute();
-
- txtAttribute.name = name;
- txtAttribute.value = new byte[value.size()];
-
- for (int i = 0; i < value.size(); ++i) {
- txtAttribute.value[i] = value.get(i).byteValue();
- }
-
- return txtAttribute;
- }
-
private static List<InetAddress> makeAddresses(String... addressStrings) {
List<InetAddress> addresses = new ArrayList<>();
@@ -750,36 +805,13 @@
return addresses;
}
- private static class TxtMatcher implements ArgumentMatcher<List<DnsTxtAttribute>> {
- private final List<DnsTxtAttribute> mAttributes;
-
- TxtMatcher(List<DnsTxtAttribute> attributes) {
- mAttributes = attributes;
- }
-
- @Override
- public boolean matches(List<DnsTxtAttribute> argument) {
- if (argument.size() != mAttributes.size()) {
- return false;
- }
- for (int i = 0; i < argument.size(); ++i) {
- if (!Objects.equals(argument.get(i).name, mAttributes.get(i).name)) {
- return false;
- }
- if (!Arrays.equals(argument.get(i).value, mAttributes.get(i).value)) {
- return false;
- }
- }
- return true;
- }
- }
-
// @Before and @Test run in different threads. NsdPublisher requires the jobs are run on the
// thread looper, so TestLooper needs to be created inside each test case to install the
// correct looper.
private void prepareTest() {
mTestLooper = new TestLooper();
Handler handler = new Handler(mTestLooper.getLooper());
- mNsdPublisher = new NsdPublisher(mMockNsdManager, handler);
+ mNsdPublisher = new NsdPublisher(mMockNsdManager, mMockDnsResolver, handler);
+ mNsdPublisher.setNetworkForHostResolution(mNetwork);
}
}
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 52a9dd9..6e2369f 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -59,22 +59,27 @@
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.IActiveOperationalDatasetReceiver;
import android.net.thread.IOperationReceiver;
+import android.net.thread.ThreadConfiguration;
import android.net.thread.ThreadNetworkException;
import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserManager;
import android.os.test.TestLooper;
import android.provider.Settings;
import android.util.AtomicFile;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.connectivity.resources.R;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.connectivity.ConnectivityResources;
+import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.MeshcopTxtAttributes;
import com.android.server.thread.openthread.testing.FakeOtDaemon;
@@ -89,7 +94,13 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import java.nio.charset.StandardCharsets;
+import java.time.Clock;
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.ZoneId;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@@ -97,6 +108,12 @@
/** Unit tests for {@link ThreadNetworkControllerService}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
+// This test doesn't really need to run on the UI thread, but @Before and @Test annotated methods
+// need to run in the same thread because there are code in {@code ThreadNetworkControllerService}
+// checking that all its methods are running in the thread of the handler it's using. This is due
+// to a bug in TestLooper that it executes all tasks on the current thread rather than the thread
+// associated to the backed Looper object.
+@UiThreadTest
public final class ThreadNetworkControllerServiceTest {
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
// Active Timestamp: 1
@@ -133,6 +150,7 @@
private static final byte[] TEST_VENDOR_OUI_BYTES = new byte[] {(byte) 0xAC, (byte) 0xDE, 0x48};
private static final String TEST_VENDOR_NAME = "test vendor";
private static final String TEST_MODEL_NAME = "test model";
+ private static final boolean TEST_VGH_VALUE = false;
@Mock private ConnectivityManager mMockConnectivityManager;
@Mock private NetworkAgent mMockNetworkAgent;
@@ -185,6 +203,8 @@
.thenReturn(TEST_VENDOR_OUI);
when(mResources.getString(eq(R.string.config_thread_model_name)))
.thenReturn(TEST_MODEL_NAME);
+ when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
+ .thenReturn(TEST_VGH_VALUE);
final AtomicFile storageFile = new AtomicFile(tempFolder.newFile("thread_settings.xml"));
mPersistentSettings = new ThreadPersistentSettings(storageFile, mConnectivityResources);
@@ -220,13 +240,15 @@
}
@Test
- public void initialize_vendorAndModelNameInResourcesAreSetToOtDaemon() throws Exception {
+ public void initialize_resourceOverlayValuesAreSetToOtDaemon() throws Exception {
when(mResources.getString(eq(R.string.config_thread_vendor_name)))
.thenReturn(TEST_VENDOR_NAME);
when(mResources.getString(eq(R.string.config_thread_vendor_oui)))
.thenReturn(TEST_VENDOR_OUI);
when(mResources.getString(eq(R.string.config_thread_model_name)))
.thenReturn(TEST_MODEL_NAME);
+ when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
+ .thenReturn(true);
mService.initialize();
mTestLooper.dispatchAll();
@@ -235,6 +257,20 @@
assertThat(meshcopTxts.vendorName).isEqualTo(TEST_VENDOR_NAME);
assertThat(meshcopTxts.vendorOui).isEqualTo(TEST_VENDOR_OUI_BYTES);
assertThat(meshcopTxts.modelName).isEqualTo(TEST_MODEL_NAME);
+ assertThat(meshcopTxts.nonStandardTxtEntries)
+ .containsExactly(new DnsTxtAttribute("vgh", "1".getBytes(StandardCharsets.UTF_8)));
+ }
+
+ @Test
+ public void getMeshcopTxtAttributes_managedByGoogleIsFalse_vghIsZero() {
+ when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
+ .thenReturn(false);
+
+ MeshcopTxtAttributes meshcopTxts =
+ ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources);
+
+ assertThat(meshcopTxts.nonStandardTxtEntries)
+ .containsExactly(new DnsTxtAttribute("vgh", "0".getBytes(StandardCharsets.UTF_8)));
}
@Test
@@ -506,9 +542,7 @@
.when(mContext)
.registerReceiver(
any(BroadcastReceiver.class),
- argThat(actualIntentFilter -> actualIntentFilter.hasAction(action)),
- any(),
- any());
+ argThat(actualIntentFilter -> actualIntentFilter.hasAction(action)));
return receiverRef;
}
@@ -528,6 +562,102 @@
}
@Test
+ public void
+ createRandomizedDataset_noNetworkTimeClock_datasetActiveTimestampIsNotAuthoritative()
+ throws Exception {
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock())
+ .thenThrow(new DateTimeException("fake throw"));
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().isAuthoritativeSource()).isFalse();
+ }
+
+ @Test
+ public void createRandomizedDataset_zeroNanoseconds_returnsZeroTicks() throws Exception {
+ Instant now = Instant.ofEpochSecond(0, 0);
+ Clock clock = Clock.fixed(now, ZoneId.systemDefault());
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock()).thenReturn(clock);
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().getTicks()).isEqualTo(0);
+ }
+
+ @Test
+ public void createRandomizedDataset_maxNanoseconds_returnsMaxTicks() throws Exception {
+ // The nanoseconds to ticks conversion is rounded in the current implementation.
+ // 32767.5 / 32768 * 1000000000 = 999984741.2109375, using 999984741 to
+ // produce the maximum ticks.
+ Instant now = Instant.ofEpochSecond(0, 999984741);
+ Clock clock = Clock.fixed(now, ZoneId.systemDefault());
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock()).thenReturn(clock);
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().getTicks()).isEqualTo(32767);
+ }
+
+ @Test
+ public void createRandomizedDataset_hasNetworkTimeClock_datasetActiveTimestampIsAuthoritative()
+ throws Exception {
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock())
+ .thenReturn(Clock.systemUTC());
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().isAuthoritativeSource()).isTrue();
+ }
+
+ @Test
public void createRandomizedDataset_succeed_activeDatasetCreated() throws Exception {
final IActiveOperationalDatasetReceiver mockReceiver =
mock(IActiveOperationalDatasetReceiver.class);
@@ -628,4 +758,35 @@
inOrder.verify(mMockTunIfController, times(1)).setInterfaceUp(false);
inOrder.verify(mMockTunIfController, times(1)).setInterfaceUp(true);
}
+
+ @Test
+ public void setConfiguration_configurationUpdated() throws Exception {
+ mService.initialize();
+ final IOperationReceiver mockReceiver1 = mock(IOperationReceiver.class);
+ final IOperationReceiver mockReceiver2 = mock(IOperationReceiver.class);
+ final IOperationReceiver mockReceiver3 = mock(IOperationReceiver.class);
+ ThreadConfiguration config1 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(false)
+ .setDhcp6PdEnabled(false)
+ .build();
+ ThreadConfiguration config2 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(true)
+ .setDhcp6PdEnabled(true)
+ .build();
+ ThreadConfiguration config3 =
+ new ThreadConfiguration.Builder(config2).build(); // Same as config2
+
+ mService.setConfiguration(config1, mockReceiver1);
+ mService.setConfiguration(config2, mockReceiver2);
+ mService.setConfiguration(config3, mockReceiver3);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPersistentSettings.getConfiguration()).isEqualTo(config3);
+ InOrder inOrder = Mockito.inOrder(mockReceiver1, mockReceiver2, mockReceiver3);
+ inOrder.verify(mockReceiver1).onSuccess();
+ inOrder.verify(mockReceiver2).onSuccess();
+ inOrder.verify(mockReceiver3).onSuccess();
+ }
}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
index 9f2d0cb..dfb3129 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
@@ -16,22 +16,29 @@
package com.android.server.thread;
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.contains;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.validateMockitoUsage;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.net.thread.ActiveOperationalDataset;
+import android.net.thread.PendingOperationalDataset;
import android.os.Binder;
-import android.os.Process;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -39,6 +46,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -49,19 +57,43 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ThreadNetworkShellCommandTest {
- private static final String TAG = "ThreadNetworkShellCommandTTest";
- @Mock ThreadNetworkControllerService mControllerService;
- @Mock ThreadNetworkCountryCode mCountryCode;
- @Mock PrintWriter mErrorWriter;
- @Mock PrintWriter mOutputWriter;
+ // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
+ // Active Timestamp: 1
+ // Channel: 19
+ // Channel Mask: 0x07FFF800
+ // Ext PAN ID: ACC214689BC40BDF
+ // Mesh Local Prefix: fd64:db12:25f4:7e0b::/64
+ // Network Key: F26B3153760F519A63BAFDDFFC80D2AF
+ // Network Name: OpenThread-d9a0
+ // PAN ID: 0xD9A0
+ // PSKc: A245479C836D551B9CA557F7B9D351B4
+ // Security Policy: 672 onrcb
+ private static final String DEFAULT_ACTIVE_DATASET_TLVS =
+ "0E080000000000010000000300001335060004001FFFE002"
+ + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
+ + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
+ + "642D643961300102D9A00410A245479C836D551B9CA557F7"
+ + "B9D351B40C0402A0FFF8";
- ThreadNetworkShellCommand mShellCommand;
+ @Mock private ThreadNetworkControllerService mControllerService;
+ @Mock private ThreadNetworkCountryCode mCountryCode;
+ @Mock private PrintWriter mErrorWriter;
+ @Mock private PrintWriter mOutputWriter;
+
+ private Context mContext;
+ private ThreadNetworkShellCommand mShellCommand;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mShellCommand = new ThreadNetworkShellCommand(mControllerService, mCountryCode);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ doNothing()
+ .when(mContext)
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
+
+ mShellCommand = new ThreadNetworkShellCommand(mContext, mControllerService, mCountryCode);
mShellCommand.setPrintWriters(mOutputWriter, mErrorWriter);
}
@@ -71,8 +103,23 @@
}
@Test
- public void getCountryCode_executeInUnrootedShell_allowed() {
- BinderUtil.setUid(Process.SHELL_UID);
+ public void getCountryCode_testingPermissionIsChecked() {
+ when(mCountryCode.getCountryCode()).thenReturn("US");
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"get-country-code"});
+
+ verify(mContext, times(1))
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
+ }
+
+ @Test
+ public void getCountryCode_currentCountryCodePrinted() {
when(mCountryCode.getCountryCode()).thenReturn("US");
mShellCommand.exec(
@@ -86,9 +133,7 @@
}
@Test
- public void forceSetCountryCodeEnabled_executeInUnrootedShell_notAllowed() {
- BinderUtil.setUid(Process.SHELL_UID);
-
+ public void forceSetCountryCodeEnabled_testingPermissionIsChecked() {
mShellCommand.exec(
new Binder(),
new FileDescriptor(),
@@ -96,14 +141,13 @@
new FileDescriptor(),
new String[] {"force-country-code", "enabled", "US"});
- verify(mCountryCode, never()).setOverrideCountryCode(eq("US"));
- verify(mErrorWriter).println(contains("force-country-code"));
+ verify(mContext, times(1))
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
}
@Test
- public void forceSetCountryCodeEnabled_executeInRootedShell_allowed() {
- BinderUtil.setUid(Process.ROOT_UID);
-
+ public void forceSetCountryCodeEnabled_countryCodeIsOverridden() {
mShellCommand.exec(
new Binder(),
new FileDescriptor(),
@@ -115,24 +159,7 @@
}
@Test
- public void forceSetCountryCodeDisabled_executeInUnrootedShell_notAllowed() {
- BinderUtil.setUid(Process.SHELL_UID);
-
- mShellCommand.exec(
- new Binder(),
- new FileDescriptor(),
- new FileDescriptor(),
- new FileDescriptor(),
- new String[] {"force-country-code", "disabled"});
-
- verify(mCountryCode, never()).setOverrideCountryCode(any());
- verify(mErrorWriter).println(contains("force-country-code"));
- }
-
- @Test
- public void forceSetCountryCodeDisabled_executeInRootedShell_allowed() {
- BinderUtil.setUid(Process.ROOT_UID);
-
+ public void forceSetCountryCodeDisabled_overriddenCountryCodeIsCleared() {
mShellCommand.exec(
new Binder(),
new FileDescriptor(),
@@ -144,9 +171,7 @@
}
@Test
- public void forceStopOtDaemon_executeInUnrootedShell_failedAndServiceApiNotCalled() {
- BinderUtil.setUid(Process.SHELL_UID);
-
+ public void forceStopOtDaemon_testingPermissionIsChecked() {
mShellCommand.exec(
new Binder(),
new FileDescriptor(),
@@ -154,14 +179,13 @@
new FileDescriptor(),
new String[] {"force-stop-ot-daemon", "enabled"});
- verify(mControllerService, never()).forceStopOtDaemonForTest(anyBoolean(), any());
- verify(mErrorWriter, atLeastOnce()).println(contains("force-stop-ot-daemon"));
- verify(mOutputWriter, never()).println();
+ verify(mContext, times(1))
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
}
@Test
public void forceStopOtDaemon_serviceThrows_failed() {
- BinderUtil.setUid(Process.ROOT_UID);
doThrow(new SecurityException(""))
.when(mControllerService)
.forceStopOtDaemonForTest(eq(true), any());
@@ -179,7 +203,6 @@
@Test
public void forceStopOtDaemon_serviceApiTimeout_failedWithTimeoutError() {
- BinderUtil.setUid(Process.ROOT_UID);
doNothing().when(mControllerService).forceStopOtDaemonForTest(eq(true), any());
mShellCommand.exec(
@@ -193,4 +216,89 @@
verify(mErrorWriter, atLeastOnce()).println(contains("timeout"));
verify(mOutputWriter, never()).println();
}
+
+ @Test
+ public void join_controllerServiceJoinIsCalled() {
+ doNothing().when(mControllerService).join(any(), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"join", DEFAULT_ACTIVE_DATASET_TLVS});
+
+ var activeDataset =
+ ActiveOperationalDataset.fromThreadTlvs(
+ base16().decode(DEFAULT_ACTIVE_DATASET_TLVS));
+ verify(mControllerService, times(1)).join(eq(activeDataset), any());
+ verify(mErrorWriter, never()).println();
+ }
+
+ @Test
+ public void join_invalidDataset_controllerServiceJoinIsNotCalled() {
+ doNothing().when(mControllerService).join(any(), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"join", "000102"});
+
+ verify(mControllerService, never()).join(any(), any());
+ verify(mErrorWriter, times(1)).println(contains("Invalid dataset argument"));
+ }
+
+ @Test
+ public void migrate_controllerServiceMigrateIsCalled() {
+ doNothing().when(mControllerService).scheduleMigration(any(), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"migrate", DEFAULT_ACTIVE_DATASET_TLVS, "300"});
+
+ ArgumentCaptor<PendingOperationalDataset> captor =
+ ArgumentCaptor.forClass(PendingOperationalDataset.class);
+ verify(mControllerService, times(1)).scheduleMigration(captor.capture(), any());
+ assertThat(captor.getValue().getActiveOperationalDataset())
+ .isEqualTo(
+ ActiveOperationalDataset.fromThreadTlvs(
+ base16().decode(DEFAULT_ACTIVE_DATASET_TLVS)));
+ assertThat(captor.getValue().getDelayTimer().toSeconds()).isEqualTo(300);
+ verify(mErrorWriter, never()).println();
+ }
+
+ @Test
+ public void migrate_invalidDataset_controllerServiceMigrateIsNotCalled() {
+ doNothing().when(mControllerService).scheduleMigration(any(), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"migrate", "000102", "300"});
+
+ verify(mControllerService, never()).scheduleMigration(any(), any());
+ verify(mErrorWriter, times(1)).println(contains("Invalid dataset argument"));
+ }
+
+ @Test
+ public void leave_controllerServiceLeaveIsCalled() {
+ doNothing().when(mControllerService).leave(any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"leave"});
+
+ verify(mControllerService, times(1)).leave(any());
+ verify(mErrorWriter, never()).println();
+ }
}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
index 7d2fe91..c932ac8 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
@@ -21,16 +21,12 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.validateMockitoUsage;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
+import android.net.thread.ThreadConfiguration;
import android.os.PersistableBundle;
import android.util.AtomicFile;
@@ -42,13 +38,14 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
/** Unit tests for {@link ThreadPersistentSettings}. */
@@ -57,12 +54,15 @@
public class ThreadPersistentSettingsTest {
private static final String TEST_COUNTRY_CODE = "CN";
- @Mock private AtomicFile mAtomicFile;
@Mock Resources mResources;
@Mock ConnectivityResources mConnectivityResources;
+ private AtomicFile mAtomicFile;
private ThreadPersistentSettings mThreadPersistentSettings;
+ @Rule(order = 0)
+ public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -70,8 +70,7 @@
when(mConnectivityResources.get()).thenReturn(mResources);
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(true);
- FileOutputStream fos = mock(FileOutputStream.class);
- when(mAtomicFile.startWrite()).thenReturn(fos);
+ mAtomicFile = createAtomicFile();
mThreadPersistentSettings =
new ThreadPersistentSettings(mAtomicFile, mConnectivityResources);
}
@@ -85,7 +84,7 @@
@Test
public void initialize_readsFromFile() throws Exception {
byte[] data = createXmlForParsing(THREAD_ENABLED.key, false);
- setupAtomicFileMockForRead(data);
+ setupAtomicFileForRead(data);
mThreadPersistentSettings.initialize();
@@ -95,7 +94,7 @@
@Test
public void initialize_ThreadDisabledInResources_returnsThreadDisabled() throws Exception {
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
- setupAtomicFileMockForRead(new byte[0]);
+ setupAtomicFileForRead(new byte[0]);
mThreadPersistentSettings.initialize();
@@ -107,7 +106,7 @@
throws Exception {
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
byte[] data = createXmlForParsing(THREAD_ENABLED.key, true);
- setupAtomicFileMockForRead(data);
+ setupAtomicFileForRead(data);
mThreadPersistentSettings.initialize();
@@ -119,9 +118,6 @@
mThreadPersistentSettings.put(THREAD_ENABLED.key, true);
assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isTrue();
- // Confirm that file writes have been triggered.
- verify(mAtomicFile).startWrite();
- verify(mAtomicFile).finishWrite(any());
}
@Test
@@ -129,9 +125,8 @@
mThreadPersistentSettings.put(THREAD_ENABLED.key, false);
assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
- // Confirm that file writes have been triggered.
- verify(mAtomicFile).startWrite();
- verify(mAtomicFile).finishWrite(any());
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
}
@Test
@@ -139,10 +134,8 @@
mThreadPersistentSettings.put(THREAD_COUNTRY_CODE.key, TEST_COUNTRY_CODE);
assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isEqualTo(TEST_COUNTRY_CODE);
-
- // Confirm that file writes have been triggered.
- verify(mAtomicFile).startWrite();
- verify(mAtomicFile).finishWrite(any());
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isEqualTo(TEST_COUNTRY_CODE);
}
@Test
@@ -150,10 +143,63 @@
mThreadPersistentSettings.put(THREAD_COUNTRY_CODE.key, null);
assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isNull();
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isNull();
+ }
- // Confirm that file writes have been triggered.
- verify(mAtomicFile).startWrite();
- verify(mAtomicFile).finishWrite(any());
+ @Test
+ public void putConfiguration_sameValues_returnsFalse() {
+ ThreadConfiguration configuration =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(true)
+ .setDhcp6PdEnabled(true)
+ .build();
+ mThreadPersistentSettings.putConfiguration(configuration);
+
+ assertThat(mThreadPersistentSettings.putConfiguration(configuration)).isFalse();
+ }
+
+ @Test
+ public void putConfiguration_differentValues_returnsTrue() {
+ ThreadConfiguration configuration1 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(false)
+ .setDhcp6PdEnabled(false)
+ .build();
+ mThreadPersistentSettings.putConfiguration(configuration1);
+ ThreadConfiguration configuration2 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(true)
+ .setDhcp6PdEnabled(true)
+ .build();
+
+ assertThat(mThreadPersistentSettings.putConfiguration(configuration2)).isTrue();
+ }
+
+ @Test
+ public void putConfiguration_nat64Enabled_valuesUpdatedAndPersisted() throws Exception {
+ ThreadConfiguration configuration =
+ new ThreadConfiguration.Builder().setNat64Enabled(true).build();
+ mThreadPersistentSettings.putConfiguration(configuration);
+
+ assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
+ }
+
+ @Test
+ public void putConfiguration_dhcp6PdEnabled_valuesUpdatedAndPersisted() throws Exception {
+ ThreadConfiguration configuration =
+ new ThreadConfiguration.Builder().setDhcp6PdEnabled(true).build();
+ mThreadPersistentSettings.putConfiguration(configuration);
+
+ assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
+ mThreadPersistentSettings.initialize();
+ assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
+ }
+
+ private AtomicFile createAtomicFile() throws Exception {
+ return new AtomicFile(mTemporaryFolder.newFile());
}
private byte[] createXmlForParsing(String key, Boolean value) throws Exception {
@@ -164,19 +210,9 @@
return outputStream.toByteArray();
}
- private void setupAtomicFileMockForRead(byte[] dataToRead) throws Exception {
- FileInputStream is = mock(FileInputStream.class);
- when(mAtomicFile.openRead()).thenReturn(is);
- when(is.available()).thenReturn(dataToRead.length).thenReturn(0);
- doAnswer(
- invocation -> {
- byte[] data = invocation.getArgument(0);
- int pos = invocation.getArgument(1);
- if (pos == dataToRead.length) return 0; // read complete.
- System.arraycopy(dataToRead, 0, data, 0, dataToRead.length);
- return dataToRead.length;
- })
- .when(is)
- .read(any(), anyInt(), anyInt());
+ private void setupAtomicFileForRead(byte[] dataToRead) throws Exception {
+ try (FileOutputStream outputStream = new FileOutputStream(mAtomicFile.getBaseFile())) {
+ outputStream.write(dataToRead);
+ }
}
}
diff --git a/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java b/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java
index bee9ceb..38a6e90 100644
--- a/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java
+++ b/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java
@@ -21,7 +21,6 @@
import static org.junit.Assume.assumeTrue;
import android.content.Context;
-import android.net.thread.ThreadNetworkManager;
import android.os.SystemProperties;
import android.os.VintfRuntimeInfo;
@@ -122,7 +121,10 @@
/** Returns {@code true} if this device has the Thread feature supported. */
private static boolean hasThreadFeature() {
final Context context = ApplicationProvider.getApplicationContext();
- return context.getSystemService(ThreadNetworkManager.class) != null;
+
+ // Use service name rather than `ThreadNetworkManager.class` to avoid
+ // `ClassNotFoundException` on U- devices.
+ return context.getSystemService("thread_network") != null;
}
/**
diff --git a/tools/aospify_device.sh b/tools/aospify_device.sh
index f25ac9d..0176093 100755
--- a/tools/aospify_device.sh
+++ b/tools/aospify_device.sh
@@ -3,10 +3,14 @@
# Script to swap core networking modules in a GMS userdebug device to AOSP modules, by remounting
# the system partition and replacing module prebuilts. This is only to be used for local testing,
# and should only be used on userdebug devices that support "adb root" and remounting the system
-# partition using overlayfs.
+# partition using overlayfs. The setup wizard should be cleared before running the script.
#
# Usage: aospify_device.sh [device_serial]
-# Reset by wiping data (adb reboot bootloader && fastboot erase userdata && fastboot reboot).
+#
+# Reset with "adb enable-verity", then wiping data (from Settings, or:
+# "adb reboot bootloader && fastboot erase userdata && fastboot reboot").
+# Some devices output errors like "Overlayfs teardown failed" on "enable-verity" but it still works
+# (/mnt/scratch should be deleted).
#
# This applies to NetworkStack, CaptivePortalLogin, dnsresolver, tethering, cellbroadcast modules,
# which generally need to be preloaded together (core networking modules + cellbroadcast which
@@ -37,6 +41,10 @@
else
rm -f /tmp/decompressed_$aosp_apex_name.apex
$ANDROID_HOST_OUT/bin/deapexer decompress --input $ANDROID_PRODUCT_OUT/system/apex/$aosp_apex_name.capex --output /tmp/decompressed_$aosp_apex_name.apex
+ if ! $ADB_CMD shell ls /system/apex/$original_apex_name.apex 1>/dev/null 2>/dev/null; then
+ # Filename observed on some phones, even though it is not actually compressed
+ original_apex_name=${original_apex_name}_compressed
+ fi
$ADB_CMD shell rm /system/apex/$original_apex_name.apex
$ADB_CMD push /tmp/decompressed_$aosp_apex_name.apex /system/apex/$aosp_apex_name.apex
rm /tmp/decompressed_$aosp_apex_name.apex
@@ -47,7 +55,7 @@
local app_type=$1
local original_apk_name=$2
local aosp_apk_name=$3
- $ADB_CMD shell rm /system/$app_type/$original_apk_name/$original_apk_name.apk
+ $ADB_CMD shell rm /system/$app_type/$original_apk_name/$original_apk_name*.apk
$ADB_CMD push $ANDROID_PRODUCT_OUT/system/$app_type/$aosp_apk_name/$aosp_apk_name.apk /system/$app_type/$original_apk_name/
}
@@ -97,7 +105,7 @@
exit 1
fi
-if ! $ADB_CMD wait-for-device shell pm path com.google.android.networkstack; then
+if ! $ADB_CMD wait-for-device shell pm path com.google.android.networkstack 1>/dev/null 2>/dev/null; then
echo "This device is already not using GMS modules"
exit 1
fi
@@ -122,8 +130,7 @@
$ADB_CMD reboot
echo "Waiting for boot..."
-$ADB_CMD wait-for-device;
-until [[ $($ADB_CMD shell getprop sys.boot_completed) == 1 ]]; do
+until [[ $($ADB_CMD wait-for-device shell getprop sys.boot_completed) == 1 ]]; do
sleep 1;
done
@@ -146,7 +153,12 @@
# Update the networkstack privapp-permissions allowlist
rm -f /tmp/pulled_privapp-permissions.xml
-$ADB_CMD pull /system/etc/permissions/privapp-permissions-google.xml /tmp/pulled_privapp-permissions.xml
+networkstack_permissions=/system/etc/permissions/GoogleNetworkStack_permissions.xml
+if ! $ADB_CMD shell ls $networkstack_permissions 1>/dev/null 2>/dev/null; then
+ networkstack_permissions=/system/etc/permissions/privapp-permissions-google.xml
+fi
+
+$ADB_CMD pull $networkstack_permissions /tmp/pulled_privapp-permissions.xml
# Remove last </permission> line, and the permissions for com.google.android.networkstack
sed -nE '1,/<\/permissions>/p' /tmp/pulled_privapp-permissions.xml \
@@ -156,7 +168,7 @@
>> /tmp/modified_privapp-permissions.xml
echo '</permissions>' >> /tmp/modified_privapp-permissions.xml
-$ADB_CMD push /tmp/modified_privapp-permissions.xml /system/etc/permissions/privapp-permissions-google.xml
+$ADB_CMD push /tmp/modified_privapp-permissions.xml $networkstack_permissions
rm /tmp/pulled_privapp-permissions.xml /tmp/modified_privapp-permissions.xml