Merge "Add firewall chains for HAPPY_BOX and user/admin PENALTY_BOX" into main
diff --git a/Cronet/tests/common/AndroidTest.xml b/Cronet/tests/common/AndroidTest.xml
index ae6b65b..7646a04 100644
--- a/Cronet/tests/common/AndroidTest.xml
+++ b/Cronet/tests/common/AndroidTest.xml
@@ -43,6 +43,8 @@
<option name="exclude-filter" value="org.chromium.net.NetworkChangesTest" />
<!-- b/316550794 -->
<option name="exclude-filter" value="org.chromium.net.impl.CronetLoggerTest#testEngineCreation" />
+ <!-- b/327182569 -->
+ <option name="exclude-filter" value="org.chromium.net.urlconnection.CronetURLStreamHandlerFactoryTest#testSetUrlStreamFactoryUsesCronetForNative" />
<option name="hidden-api-checks" value="false"/>
<option name="isolated-storage" value="false"/>
<option name="orchestrator" value="true"/>
diff --git a/Cronet/tests/mts/AndroidTest.xml b/Cronet/tests/mts/AndroidTest.xml
index 5aed655..a438e2e 100644
--- a/Cronet/tests/mts/AndroidTest.xml
+++ b/Cronet/tests/mts/AndroidTest.xml
@@ -43,6 +43,8 @@
<option name="exclude-filter" value="org.chromium.net.NetworkChangesTest" />
<!-- b/316550794 -->
<option name="exclude-filter" value="org.chromium.net.impl.CronetLoggerTest#testEngineCreation" />
+ <!-- b/327182569 -->
+ <option name="exclude-filter" value="org.chromium.net.urlconnection.CronetURLStreamHandlerFactoryTest#testSetUrlStreamFactoryUsesCronetForNative" />
<option name="hidden-api-checks" value="false"/>
<option name="isolated-storage" value="false"/>
<option name="orchestrator" value="true"/>
@@ -53,4 +55,4 @@
class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 4bae221..304a6ed 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -113,6 +113,7 @@
prebuilts: [
"current_sdkinfo",
"netbpfload.mainline.rc",
+ "netbpfload.35rc",
"ot-daemon.init.34rc",
],
manifest: "manifest.json",
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 4f152bf..8e72747 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -876,5 +876,5 @@
}
LICENSE("Apache 2.0");
-CRITICAL("Connectivity (Tethering)");
+//CRITICAL("Connectivity (Tethering)");
DISABLE_BTF_ON_USER_BUILDS();
diff --git a/framework/Android.bp b/framework/Android.bp
index 8787167..deb1c5a 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -117,7 +117,6 @@
static_libs: [
"httpclient_api",
"httpclient_impl",
- "http_client_logging",
// Framework-connectivity-pre-jarjar is identical to framework-connectivity
// implementation, but without the jarjar rules. However, framework-connectivity
// is not based on framework-connectivity-pre-jarjar, it's rebuilt from source
@@ -147,7 +146,6 @@
],
impl_only_static_libs: [
"httpclient_impl",
- "http_client_logging",
],
}
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
index c39b46c..f278695 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -75,3 +75,10 @@
filename: "netbpfload.33rc",
installable: false,
}
+
+prebuilt_etc {
+ name: "netbpfload.35rc",
+ src: "netbpfload.35rc",
+ filename: "netbpfload.35rc",
+ installable: false,
+}
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index 83bb98c..710782d 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -313,7 +313,7 @@
return 1;
}
- if (false && isAtLeastV) {
+ if (isAtLeastV) {
// 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
@@ -380,7 +380,7 @@
return 1;
}
- if (false && isAtLeastV) {
+ if (isAtLeastV) {
ALOGI("done, transferring control to platform bpfloader.");
const char * args[] = { platformBpfLoader, NULL, };
diff --git a/netbpfload/loader.cpp b/netbpfload/loader.cpp
index 9dd0d2a..52428a3 100644
--- a/netbpfload/loader.cpp
+++ b/netbpfload/loader.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "NetBpfLoader"
+#define LOG_TAG "NetBpfLoad"
#include <errno.h>
#include <fcntl.h>
@@ -769,7 +769,7 @@
.max_entries = max_entries,
.map_flags = md[i].map_flags,
};
- if (isAtLeastKernelVersion(4, 14, 0))
+ if (isAtLeastKernelVersion(4, 15, 0))
strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
fd.reset(bpf(BPF_MAP_CREATE, req));
saved_errno = errno;
@@ -1012,7 +1012,7 @@
.log_size = static_cast<__u32>(log_buf.size()),
.expected_attach_type = cs[i].expected_attach_type,
};
- if (isAtLeastKernelVersion(4, 14, 0))
+ if (isAtLeastKernelVersion(4, 15, 0))
strlcpy(req.prog_name, cs[i].name.c_str(), sizeof(req.prog_name));
fd.reset(bpf(BPF_PROG_LOAD, req));
diff --git a/netbpfload/netbpfload.35rc b/netbpfload/netbpfload.35rc
new file mode 100644
index 0000000..0fbcb5a
--- /dev/null
+++ b/netbpfload/netbpfload.35rc
@@ -0,0 +1,9 @@
+service bpfloader /apex/com.android.tethering/bin/netbpfload
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
+ user root
+ file /dev/kmsg w
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ reboot_on_failure reboot,bpfloader-failed
+ override
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 0d75c05..b535ebf 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -183,7 +183,8 @@
// Make sure BPF programs are loaded before doing anything
ALOGI("Waiting for BPF programs");
- if (true || !modules::sdklevel::IsAtLeastV()) {
+ // TODO: use !modules::sdklevel::IsAtLeastV() once api finalized
+ if (android_get_device_api_level() < __ANDROID_API_V__) {
waitForNetProgsLoaded();
ALOGI("Networking BPF programs are loaded");
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 2e258ab..7c1ca30 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1825,12 +1825,20 @@
* <p>For now NsdService only allows single-label hostnames conforming to RFC 1035. In other
* words, the hostname should be at most 63 characters long and it only contains letters, digits
* and hyphens.
+ *
+ * <p>Additionally, this allows hostname starting with a digit to support Matter devices. Per
+ * Matter spec 4.3.1.1:
+ *
+ * <p>The target host name SHALL be constructed using one of the available link-layer addresses,
+ * such as a 48-bit device MAC address (for Ethernet and Wi‑Fi) or a 64-bit MAC Extended Address
+ * (for Thread) expressed as a fixed-length twelve-character (or sixteen-character) hexadecimal
+ * string, encoded as ASCII (UTF-8) text using capital letters, e.g., B75AFB458ECD.<domain>.
*/
public static boolean checkHostname(@Nullable String hostname) {
if (hostname == null) {
return true;
}
- String HOSTNAME_REGEX = "^[a-zA-Z]([a-zA-Z0-9-_]{0,61}[a-zA-Z0-9])?$";
+ String HOSTNAME_REGEX = "^[a-zA-Z0-9]([a-zA-Z0-9-_]{0,61}[a-zA-Z0-9])?$";
return Pattern.compile(HOSTNAME_REGEX).matcher(hostname).matches();
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index 073e465..eb85110 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -97,6 +97,8 @@
@NonNull
private final Looper mLooper;
@NonNull
+ private final Dependencies mDeps;
+ @NonNull
private final String[] mDeviceHostname;
@NonNull
private final MdnsFeatureFlags mMdnsFeatureFlags;
@@ -111,6 +113,7 @@
@NonNull String[] deviceHostname, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
mDeviceHostname = deviceHostname;
mLooper = looper;
+ mDeps = deps;
mMdnsFeatureFlags = mdnsFeatureFlags;
}
@@ -127,6 +130,10 @@
public Enumeration<InetAddress> getInterfaceInetAddresses(@NonNull NetworkInterface iface) {
return iface.getInetAddresses();
}
+
+ public long elapsedRealTime() {
+ return SystemClock.elapsedRealtime();
+ }
}
private static class RecordInfo<T extends MdnsRecord> {
@@ -140,17 +147,25 @@
public final boolean isSharedName;
/**
- * Last time (as per SystemClock.elapsedRealtime) when advertised via multicast, 0 if never
+ * Last time (as per SystemClock.elapsedRealtime) when advertised via multicast on IPv4, 0
+ * if never
*/
- public long lastAdvertisedTimeMs;
+ public long lastAdvertisedOnIpv4TimeMs;
/**
- * Last time (as per SystemClock.elapsedRealtime) when sent via unicast or multicast,
- * 0 if never
+ * Last time (as per SystemClock.elapsedRealtime) when advertised via multicast on IPv6, 0
+ * if never
*/
- // FIXME: the `lastSentTimeMs` and `lastAdvertisedTimeMs` should be maintained separately
- // for IPv4 and IPv6, because neither IPv4 nor and IPv6 clients can receive replies in
- // different address space.
+ public long lastAdvertisedOnIpv6TimeMs;
+
+ /**
+ * Last time (as per SystemClock.elapsedRealtime) when sent via unicast or multicast, 0 if
+ * never.
+ *
+ * <p>Different from lastAdvertisedOnIpv(4|6)TimeMs, lastSentTimeMs is mainly used for
+ * tracking is a record is ever sent out, no matter unicast/multicast or IPv4/IPv6. It's
+ * unnecessary to maintain two versions (IPv4/IPv6) for it.
+ */
public long lastSentTimeMs;
RecordInfo(NsdServiceInfo serviceInfo, T record, boolean sharedName) {
@@ -577,7 +592,8 @@
*/
@Nullable
public MdnsReplyInfo getReply(MdnsPacket packet, InetSocketAddress src) {
- final long now = SystemClock.elapsedRealtime();
+ final long now = mDeps.elapsedRealTime();
+ final boolean isQuestionOnIpv4 = src.getAddress() instanceof Inet4Address;
// TODO: b/322142420 - Set<RecordInfo<?>> may contain duplicate records wrapped in different
// RecordInfo<?>s when custom host is enabled.
@@ -595,7 +611,7 @@
null /* serviceSrvRecord */, null /* serviceTxtRecord */,
null /* hostname */,
replyUnicastEnabled, now, answerInfo, additionalAnswerInfo,
- Collections.emptyList())) {
+ Collections.emptyList(), isQuestionOnIpv4)) {
replyUnicast &= question.isUnicastReplyRequested();
}
@@ -607,7 +623,7 @@
registration.srvRecord, registration.txtRecord,
registration.serviceInfo.getHostname(),
replyUnicastEnabled, now,
- answerInfo, additionalAnswerInfo, packet.answers)) {
+ answerInfo, additionalAnswerInfo, packet.answers, isQuestionOnIpv4)) {
replyUnicast &= question.isUnicastReplyRequested();
registration.repliedServiceCount++;
registration.sentPacketCount++;
@@ -685,7 +701,7 @@
// multicast responses. Unicast replies are faster as they do not need to wait for the
// beacon interval on Wi-Fi.
dest = src;
- } else if (src.getAddress() instanceof Inet4Address) {
+ } else if (isQuestionOnIpv4) {
dest = IPV4_SOCKET_ADDR;
} else {
dest = IPV6_SOCKET_ADDR;
@@ -697,7 +713,11 @@
// TODO: consider actual packet send delay after response aggregation
info.lastSentTimeMs = now + delayMs;
if (!replyUnicast) {
- info.lastAdvertisedTimeMs = info.lastSentTimeMs;
+ if (isQuestionOnIpv4) {
+ info.lastAdvertisedOnIpv4TimeMs = info.lastSentTimeMs;
+ } else {
+ info.lastAdvertisedOnIpv6TimeMs = info.lastSentTimeMs;
+ }
}
// Different RecordInfos may the contain the same record
if (!answerRecords.contains(info.record)) {
@@ -729,7 +749,8 @@
@Nullable String hostname,
boolean replyUnicastEnabled, long now, @NonNull Set<RecordInfo<?>> answerInfo,
@NonNull Set<RecordInfo<?>> additionalAnswerInfo,
- @NonNull List<MdnsRecord> knownAnswerRecords) {
+ @NonNull List<MdnsRecord> knownAnswerRecords,
+ boolean isQuestionOnIpv4) {
boolean hasDnsSdPtrRecordAnswer = false;
boolean hasDnsSdSrvRecordAnswer = false;
boolean hasFullyOwnedNameMatch = false;
@@ -778,10 +799,20 @@
// TODO: responses to probe queries should bypass this check and only ensure the
// reply is sent 250ms after the last sent time (RFC 6762 p.15)
- if (!(replyUnicastEnabled && question.isUnicastReplyRequested())
- && info.lastAdvertisedTimeMs > 0L
- && now - info.lastAdvertisedTimeMs < MIN_MULTICAST_REPLY_INTERVAL_MS) {
- continue;
+ if (!(replyUnicastEnabled && question.isUnicastReplyRequested())) {
+ if (isQuestionOnIpv4) { // IPv4
+ if (info.lastAdvertisedOnIpv4TimeMs > 0L
+ && now - info.lastAdvertisedOnIpv4TimeMs
+ < MIN_MULTICAST_REPLY_INTERVAL_MS) {
+ continue;
+ }
+ } else { // IPv6
+ if (info.lastAdvertisedOnIpv6TimeMs > 0L
+ && now - info.lastAdvertisedOnIpv6TimeMs
+ < MIN_MULTICAST_REPLY_INTERVAL_MS) {
+ continue;
+ }
+ }
}
answerInfo.add(info);
@@ -1302,10 +1333,11 @@
final ServiceRegistration registration = mServices.get(serviceId);
if (registration == null) return;
- final long now = SystemClock.elapsedRealtime();
+ final long now = mDeps.elapsedRealTime();
for (RecordInfo<?> record : registration.allRecords) {
record.lastSentTimeMs = now;
- record.lastAdvertisedTimeMs = now;
+ record.lastAdvertisedOnIpv4TimeMs = now;
+ record.lastAdvertisedOnIpv6TimeMs = now;
}
registration.sentPacketCount += sentPacketCount;
}
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
index b8689d6..92f1953 100644
--- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -108,7 +108,11 @@
PermissionUtils.enforceRestrictedNetworkPermission(mContext, TAG);
}
- return new IpConfiguration(mTracker.getIpConfiguration(iface));
+ // This causes thread-unsafe access on mIpConfigurations which might
+ // race with calls to EthernetManager#updateConfiguration().
+ // EthernetManager#getConfiguration() has been marked as
+ // @UnsupportedAppUsage since Android R.
+ return mTracker.getIpConfiguration(iface);
}
/**
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 71f289e..a60592f 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -31,8 +31,6 @@
import android.net.ITetheredInterfaceCallback;
import android.net.InterfaceConfigurationParcel;
import android.net.IpConfiguration;
-import android.net.IpConfiguration.IpAssignment;
-import android.net.IpConfiguration.ProxySettings;
import android.net.LinkAddress;
import android.net.NetworkCapabilities;
import android.net.StaticIpConfiguration;
@@ -111,6 +109,7 @@
/** Mapping between {iface name | mac address} -> {NetworkCapabilities} */
private final ConcurrentHashMap<String, NetworkCapabilities> mNetworkCapabilities =
new ConcurrentHashMap<>();
+ /** Mapping between {iface name | mac address} -> {IpConfiguration} */
private final ConcurrentHashMap<String, IpConfiguration> mIpConfigurations =
new ConcurrentHashMap<>();
@@ -298,7 +297,7 @@
}
private IpConfiguration getIpConfigurationForCallback(String iface, int state) {
- return (state == EthernetManager.STATE_ABSENT) ? null : getOrCreateIpConfiguration(iface);
+ return (state == EthernetManager.STATE_ABSENT) ? null : getIpConfiguration(iface);
}
private void ensureRunningOnEthernetServiceThread() {
@@ -391,8 +390,83 @@
mHandler.post(() -> setInterfaceAdministrativeState(iface, enabled, cb));
}
- IpConfiguration getIpConfiguration(String iface) {
- return mIpConfigurations.get(iface);
+ private @Nullable String getHwAddress(String iface) {
+ if (getInterfaceRole(iface) == EthernetManager.ROLE_SERVER) {
+ return mTetheringInterfaceHwAddr;
+ }
+
+ return mFactory.getHwAddress(iface);
+ }
+
+ /**
+ * Get the IP configuration of the interface, or the default if the interface doesn't exist.
+ * @param iface the name of the interface to retrieve.
+ *
+ * @return The IP configuration
+ */
+ public IpConfiguration getIpConfiguration(String iface) {
+ return getIpConfiguration(iface, getHwAddress(iface));
+ }
+
+ private IpConfiguration getIpConfiguration(String iface, @Nullable String hwAddress) {
+ // Look up Ip configuration first by ifname, then by MAC address.
+ IpConfiguration ipConfig = mIpConfigurations.get(iface);
+ if (ipConfig != null) {
+ return ipConfig;
+ }
+
+ if (hwAddress == null) {
+ // should never happen.
+ Log.wtf(TAG, "No hardware address for interface " + iface);
+ } else {
+ ipConfig = mIpConfigurations.get(hwAddress);
+ }
+
+ if (ipConfig == null) {
+ ipConfig = new IpConfiguration.Builder().build();
+ }
+
+ return ipConfig;
+ }
+
+ private NetworkCapabilities getNetworkCapabilities(String iface) {
+ return getNetworkCapabilities(iface, getHwAddress(iface));
+ }
+
+ private NetworkCapabilities getNetworkCapabilities(String iface, @Nullable String hwAddress) {
+ // Look up network capabilities first by ifname, then by MAC address.
+ NetworkCapabilities networkCapabilities = mNetworkCapabilities.get(iface);
+ if (networkCapabilities != null) {
+ return networkCapabilities;
+ }
+
+ if (hwAddress == null) {
+ // should never happen.
+ Log.wtf(TAG, "No hardware address for interface " + iface);
+ } else {
+ networkCapabilities = mNetworkCapabilities.get(hwAddress);
+ }
+
+ if (networkCapabilities != null) {
+ return networkCapabilities;
+ }
+
+ final NetworkCapabilities.Builder builder = createNetworkCapabilities(
+ false /* clear default capabilities */, null, null)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+
+ if (isValidTestInterface(iface)) {
+ builder.addTransportType(NetworkCapabilities.TRANSPORT_TEST);
+ } else {
+ builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ }
+
+ return builder.build();
}
@VisibleForTesting(visibility = PACKAGE)
@@ -433,8 +507,8 @@
* NET_CAPABILITY_NOT_RESTRICTED) capability. Otherwise, returns false.
*/
boolean isRestrictedInterface(String iface) {
- final NetworkCapabilities nc = mNetworkCapabilities.get(iface);
- return nc != null && !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ final NetworkCapabilities nc = getNetworkCapabilities(iface);
+ return !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
}
void addListener(IEthernetServiceListener listener, boolean canUseRestrictedNetworks) {
@@ -623,17 +697,9 @@
return;
}
- NetworkCapabilities nc = mNetworkCapabilities.get(iface);
- if (nc == null) {
- // Try to resolve using mac address
- nc = mNetworkCapabilities.get(hwAddress);
- if (nc == null) {
- final boolean isTestIface = iface.matches(TEST_IFACE_REGEXP);
- nc = createDefaultNetworkCapabilities(isTestIface);
- }
- }
+ final NetworkCapabilities nc = getNetworkCapabilities(iface, hwAddress);
+ final IpConfiguration ipConfiguration = getIpConfiguration(iface, hwAddress);
- IpConfiguration ipConfiguration = getOrCreateIpConfiguration(iface);
Log.d(TAG, "Tracking interface in client mode: " + iface);
mFactory.addInterface(iface, hwAddress, ipConfiguration, nc);
@@ -773,25 +839,6 @@
return new EthernetTrackerConfig(configString.split(";", /* limit of tokens */ 4));
}
- private static NetworkCapabilities createDefaultNetworkCapabilities(boolean isTestIface) {
- NetworkCapabilities.Builder builder = createNetworkCapabilities(
- false /* clear default capabilities */, null, null)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
-
- if (isTestIface) {
- builder.addTransportType(NetworkCapabilities.TRANSPORT_TEST);
- } else {
- builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
- }
-
- return builder.build();
- }
-
/**
* Parses a static list of network capabilities
*
@@ -926,15 +973,6 @@
return new IpConfiguration.Builder().setStaticIpConfiguration(staticIpConfig).build();
}
- private IpConfiguration getOrCreateIpConfiguration(String iface) {
- IpConfiguration ret = mIpConfigurations.get(iface);
- if (ret != null) return ret;
- ret = new IpConfiguration();
- ret.setIpAssignment(IpAssignment.DHCP);
- ret.setProxySettings(ProxySettings.NONE);
- return ret;
- }
-
private boolean isValidEthernetInterface(String iface) {
return iface.matches(mIfaceMatch) || isValidTestInterface(iface);
}
@@ -1021,7 +1059,7 @@
pw.println("IP Configurations:");
pw.increaseIndent();
for (String iface : mIpConfigurations.keySet()) {
- pw.println(iface + ": " + mIpConfigurations.get(iface));
+ pw.println(iface + ": " + getIpConfiguration(iface));
}
pw.decreaseIndent();
pw.println();
@@ -1029,7 +1067,7 @@
pw.println("Network Capabilities:");
pw.increaseIndent();
for (String iface : mNetworkCapabilities.keySet()) {
- pw.println(iface + ": " + mNetworkCapabilities.get(iface));
+ pw.println(iface + ": " + getNetworkCapabilities(iface));
}
pw.decreaseIndent();
pw.println();
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index 4214bc9..c07d050 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -114,7 +114,8 @@
V("/sys/fs/bpf", S_IFDIR|S_ISVTX|0777, ROOT, ROOT, "fs_bpf", DIR);
- if (false && modules::sdklevel::IsAtLeastV()) {
+ // 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);
diff --git a/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java b/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
index 4d5001b..ac479b8 100644
--- a/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
+++ b/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
@@ -27,6 +27,8 @@
import static android.system.OsConstants.SOCK_NONBLOCK;
import static android.system.OsConstants.SOCK_RAW;
+import static com.android.net.module.util.CollectionUtils.getIndexForValue;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.MulticastRoutingConfig;
@@ -150,7 +152,7 @@
}
private Integer getInterfaceIndex(String ifName) {
- int mapIndex = mInterfaces.indexOfValue(ifName);
+ int mapIndex = getIndexForValue(mInterfaces, ifName);
if (mapIndex < 0) return null;
return mInterfaces.keyAt(mapIndex);
}
@@ -246,7 +248,7 @@
if (virtualIndex == null) return;
updateMfcs();
- mInterfaces.removeAt(mInterfaces.indexOfValue(ifName));
+ mInterfaces.removeAt(getIndexForValue(mInterfaces, ifName));
mVirtualInterfaces.remove(virtualIndex);
try {
mDependencies.setsockoptMrt6DelMif(mMulticastRoutingFd, virtualIndex);
@@ -270,7 +272,7 @@
@VisibleForTesting
public Integer getVirtualInterfaceIndex(String ifName) {
- int mapIndex = mVirtualInterfaces.indexOfValue(ifName);
+ int mapIndex = getIndexForValue(mVirtualInterfaces, ifName);
if (mapIndex < 0) return null;
return mVirtualInterfaces.keyAt(mapIndex);
}
@@ -291,7 +293,7 @@
private void maybeAddAndTrackInterface(String ifName) {
checkOnHandlerThread();
- if (mVirtualInterfaces.indexOfValue(ifName) >= 0) return;
+ if (getIndexForValue(mVirtualInterfaces, ifName) >= 0) return;
int nextVirtualIndex = getNextAvailableVirtualIndex();
int ifIndex = mDependencies.getInterfaceIndex(ifName);
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
index 27e1a32..1896de6 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
@@ -210,6 +210,7 @@
case RTM_NEWRULE: return "RTM_NEWRULE";
case RTM_DELRULE: return "RTM_DELRULE";
case RTM_GETRULE: return "RTM_GETRULE";
+ case RTM_NEWPREFIX: return "RTM_NEWPREFIX";
case RTM_NEWNDUSEROPT: return "RTM_NEWNDUSEROPT";
default: return "unknown RTM type: " + String.valueOf(nlmType);
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructPrefixCacheInfo.java b/staticlibs/device/com/android/net/module/util/netlink/StructPrefixCacheInfo.java
new file mode 100644
index 0000000..cfaa6e1
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructPrefixCacheInfo.java
@@ -0,0 +1,77 @@
+/*
+ * 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.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct prefix_cacheinfo {
+ * __u32 preferred_time;
+ * __u32 valid_time;
+ * }
+ *
+ * see also:
+ *
+ * include/uapi/linux/if_addr.h
+ *
+ * @hide
+ */
+public class StructPrefixCacheInfo extends Struct {
+ public static final int STRUCT_SIZE = 8;
+
+ @Field(order = 0, type = Type.U32)
+ public final long preferred_time;
+ @Field(order = 1, type = Type.U32)
+ public final long valid_time;
+
+ StructPrefixCacheInfo(long preferred, long valid) {
+ this.preferred_time = preferred;
+ this.valid_time = valid;
+ }
+
+ /**
+ * Parse a prefix_cacheinfo struct from a {@link ByteBuffer}.
+ *
+ * @param byteBuffer The buffer from which to parse the prefix_cacheinfo.
+ * @return the parsed prefix_cacheinfo struct, or throw IllegalArgumentException if the
+ * prefix_cacheinfo struct could not be parsed successfully(for example, if it was
+ * truncated).
+ */
+ public static StructPrefixCacheInfo parse(@NonNull final ByteBuffer byteBuffer) {
+ if (byteBuffer.remaining() < STRUCT_SIZE) {
+ throw new IllegalArgumentException("Invalid bytebuffer remaining size "
+ + byteBuffer.remaining() + " for prefix_cacheinfo attribute");
+ }
+
+ // The ByteOrder must already have been set to native order.
+ return Struct.parse(StructPrefixCacheInfo.class, byteBuffer);
+ }
+
+ /**
+ * Write a prefix_cacheinfo struct to {@link ByteBuffer}.
+ */
+ public void pack(@NonNull final ByteBuffer byteBuffer) {
+ // The ByteOrder must already have been set to native order.
+ writeToByteBuffer(byteBuffer);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructPrefixMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructPrefixMsg.java
new file mode 100644
index 0000000..504d6c7
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructPrefixMsg.java
@@ -0,0 +1,94 @@
+/*
+ * 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.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct prefixmsg {
+ * unsigned char prefix_family;
+ * unsigned char prefix_pad1;
+ * unsigned short prefix_pad2;
+ * int prefix_ifindex;
+ * unsigned char prefix_type;
+ * unsigned char prefix_len;
+ * unsigned char prefix_flags;
+ * unsigned char prefix_pad3;
+ * }
+ *
+ * see also:
+ *
+ * include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class StructPrefixMsg extends Struct {
+ // Already aligned.
+ public static final int STRUCT_SIZE = 12;
+
+ @Field(order = 0, type = Type.U8, padding = 3)
+ public final short prefix_family;
+ @Field(order = 1, type = Type.S32)
+ public final int prefix_ifindex;
+ @Field(order = 2, type = Type.U8)
+ public final short prefix_type;
+ @Field(order = 3, type = Type.U8)
+ public final short prefix_len;
+ @Field(order = 4, type = Type.U8, padding = 1)
+ public final short prefix_flags;
+
+ @VisibleForTesting
+ public StructPrefixMsg(short family, int ifindex, short type, short len, short flags) {
+ this.prefix_family = family;
+ this.prefix_ifindex = ifindex;
+ this.prefix_type = type;
+ this.prefix_len = len;
+ this.prefix_flags = flags;
+ }
+
+ /**
+ * Parse a prefixmsg struct from a {@link ByteBuffer}.
+ *
+ * @param byteBuffer The buffer from which to parse the prefixmsg.
+ * @return the parsed prefixmsg struct, or throw IllegalArgumentException if the prefixmsg
+ * struct could not be parsed successfully (for example, if it was truncated).
+ */
+ public static StructPrefixMsg parse(@NonNull final ByteBuffer byteBuffer) {
+ if (byteBuffer.remaining() < STRUCT_SIZE) {
+ throw new IllegalArgumentException("Invalid bytebuffer remaining size "
+ + byteBuffer.remaining() + "for prefix_msg struct.");
+ }
+
+ // The ByteOrder must already have been set to native order.
+ return Struct.parse(StructPrefixMsg.class, byteBuffer);
+ }
+
+ /**
+ * Write a prefixmsg struct to {@link ByteBuffer}.
+ */
+ public void pack(@NonNull final ByteBuffer byteBuffer) {
+ // The ByteOrder must already have been set to native order.
+ writeToByteBuffer(byteBuffer);
+ }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
index 39e7ce9..f3d8c4a 100644
--- a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
@@ -389,4 +389,28 @@
}
return dest;
}
+
+ /**
+ * Returns an index of the given SparseArray that contains the given value, or -1
+ * number if no keys map to the given value.
+ *
+ * <p>Note this is a linear search, and if multiple keys can map to the same value
+ * then the smallest index is returned.
+ *
+ * <p>This function compares values with {@code equals} while the
+ * {@link SparseArray#indexOfValue} compares values using {@code ==}.
+ */
+ public static <T> int getIndexForValue(SparseArray<T> sparseArray, T value) {
+ for(int i = 0, nsize = sparseArray.size(); i < nsize; i++) {
+ T valueAt = sparseArray.valueAt(i);
+ if (valueAt == null) {
+ if (value == null) {
+ return i;
+ };
+ } else if (valueAt.equals(value)) {
+ return i;
+ }
+ }
+ return -1;
+ }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
index e23f999..4ed3afd 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
@@ -16,6 +16,7 @@
package com.android.net.module.util
+import android.util.SparseArray
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.testutils.assertThrows
@@ -179,4 +180,20 @@
CollectionUtils.assoc(listOf(1, 2), list15)
}
}
+
+ @Test
+ fun testGetIndexForValue() {
+ val sparseArray = SparseArray<String>();
+ sparseArray.put(5, "hello");
+ sparseArray.put(10, "abcd");
+ sparseArray.put(20, null);
+
+ val value1 = "abcd";
+ val value1Copy = String(value1.toCharArray())
+ val value2 = null;
+
+ assertEquals(1, CollectionUtils.getIndexForValue(sparseArray, value1));
+ assertEquals(1, CollectionUtils.getIndexForValue(sparseArray, value1Copy));
+ assertEquals(2, CollectionUtils.getIndexForValue(sparseArray, value2));
+ }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java
index 143e4d4..e42c552 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java
@@ -46,6 +46,7 @@
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWADDR;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNDUSEROPT;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWPREFIX;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWROUTE;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWRULE;
@@ -89,6 +90,7 @@
assertEquals("RTM_NEWRULE", stringForNlMsgType(RTM_NEWRULE, NETLINK_ROUTE));
assertEquals("RTM_DELRULE", stringForNlMsgType(RTM_DELRULE, NETLINK_ROUTE));
assertEquals("RTM_GETRULE", stringForNlMsgType(RTM_GETRULE, NETLINK_ROUTE));
+ assertEquals("RTM_NEWPREFIX", stringForNlMsgType(RTM_NEWPREFIX, NETLINK_ROUTE));
assertEquals("RTM_NEWNDUSEROPT", stringForNlMsgType(RTM_NEWNDUSEROPT, NETLINK_ROUTE));
assertEquals("SOCK_DIAG_BY_FAMILY",
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 9124ac0..3843b90 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -99,6 +99,8 @@
"mcts-networking",
"mts-tethering",
"mcts-tethering",
+ "mcts-wifi",
+ "mcts-dnsresolver",
],
data: [":ConnectivityTestPreparer"],
}
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 3be44f7..e10a06c 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -22,35 +22,55 @@
import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.content.pm.PackageManager.FEATURE_WIFI
import android.net.ConnectivityManager
+import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.net.apf.ApfCapabilities
import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
import android.os.PowerManager
import android.platform.test.annotations.AppModeFull
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
+import android.system.Os
import android.system.OsConstants
+import android.system.OsConstants.AF_INET6
+import android.system.OsConstants.IPPROTO_ICMPV6
+import android.system.OsConstants.SOCK_DGRAM
+import android.system.OsConstants.SOCK_NONBLOCK
+import android.util.Log
+import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
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.internal.util.HexDump
+import com.android.net.module.util.PacketReader
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.NetworkStackModuleTest
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import com.android.testutils.SkipPresubmit
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.runAsShell
+import com.android.testutils.waitForIdle
import com.google.common.truth.Truth.assertThat
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.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
import kotlin.random.Random
+import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import org.junit.After
+import org.junit.AfterClass
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Rule
@@ -61,16 +81,56 @@
private const val TIMEOUT_MS = 2000L
private const val APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version"
private const val POLLING_INTERVAL_MS: Int = 100
+private const val RCV_BUFFER_SIZE = 1480
@AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
@RunWith(DevSdkIgnoreRunner::class)
+@RequiresDevice
@NetworkStackModuleTest
+// ByteArray.toHexString is experimental API
+@kotlin.ExperimentalStdlibApi
class ApfIntegrationTest {
companion object {
+ private val PING_DESTINATION = InetSocketAddress("2001:4860:4860::8888", 0)
+
+ private val context = InstrumentationRegistry.getInstrumentation().context
+ private val powerManager = context.getSystemService(PowerManager::class.java)!!
+ private val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG)
+
+ fun pollingCheck(condition: () -> Boolean, timeout_ms: Int): Boolean {
+ var polling_time = 0
+ do {
+ Thread.sleep(POLLING_INTERVAL_MS.toLong())
+ polling_time += POLLING_INTERVAL_MS
+ if (condition()) return true
+ } while (polling_time < timeout_ms)
+ return false
+ }
+
+ fun turnScreenOff() {
+ if (!wakeLock.isHeld()) wakeLock.acquire()
+ runShellCommandOrThrow("input keyevent KEYCODE_SLEEP")
+ val result = pollingCheck({ !powerManager.isInteractive() }, timeout_ms = 2000)
+ assertThat(result).isTrue()
+ }
+
+ fun turnScreenOn() {
+ if (wakeLock.isHeld()) wakeLock.release()
+ runShellCommandOrThrow("input keyevent KEYCODE_WAKEUP")
+ val result = pollingCheck({ powerManager.isInteractive() }, timeout_ms = 2000)
+ assertThat(result).isTrue()
+ }
+
@BeforeClass
@JvmStatic
@Suppress("ktlint:standard:no-multi-spaces")
fun setupOnce() {
+ // TODO: assertions thrown in @BeforeClass / @AfterClass are not well supported in the
+ // test infrastructure. Consider saving excepion and throwing it in setUp().
+ // APF must run when the screen is off and the device is not interactive.
+ turnScreenOff()
+ // Wait for APF to become active.
+ Thread.sleep(1000)
// TODO: check that there is no active wifi network. Otherwise, ApfFilter has already been
// created.
// APF adb cmds are only implemented in ApfFilter.java. Enable experiment to prevent
@@ -84,19 +144,96 @@
)
}
}
+
+ @AfterClass
+ @JvmStatic
+ fun tearDownOnce() {
+ turnScreenOn()
+ }
+ }
+
+ class Icmp6PacketReader(
+ handler: Handler,
+ private val network: Network
+ ) : PacketReader(handler, RCV_BUFFER_SIZE) {
+ private var sockFd: FileDescriptor? = null
+ private var futureReply: CompletableFuture<ByteArray>? = null
+
+ override fun createFd(): FileDescriptor {
+ // sockFd is closed by calling super.stop()
+ val sock = Os.socket(AF_INET6, SOCK_DGRAM or SOCK_NONBLOCK, IPPROTO_ICMPV6)
+ // APF runs only on WiFi, so make sure the socket is bound to the right network.
+ network.bindSocket(sock)
+ sockFd = sock
+ return sock
+ }
+
+ override fun handlePacket(recvbuf: ByteArray, length: Int) {
+ // If zero-length or Type is not echo reply: ignore.
+ if (length == 0 || recvbuf[0] != 0x81.toByte()) {
+ return
+ }
+ // Only copy the ping data and complete the future.
+ val result = recvbuf.sliceArray(8..<length)
+ Log.i(TAG, "Received ping reply: ${result.toHexString()}")
+ futureReply!!.complete(recvbuf.sliceArray(8..<length))
+ }
+
+ fun sendPing(data: ByteArray) {
+ require(data.size == 56)
+
+ // rfc4443#section-4.1: Echo Request Message
+ // 0 1 2 3
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Type | Code | Checksum |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Identifier | Sequence Number |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Data ...
+ // +-+-+-+-+-
+ val icmp6Header = byteArrayOf(0x80.toByte(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
+ val packet = icmp6Header + data
+ Log.i(TAG, "Sent ping: ${packet.toHexString()}")
+ futureReply = CompletableFuture<ByteArray>()
+ Os.sendto(sockFd!!, packet, 0, packet.size, 0, PING_DESTINATION)
+ }
+
+ fun expectPingReply(): ByteArray {
+ return futureReply!!.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+
+ fun expectPingDropped() {
+ assertFailsWith(TimeoutException::class) {
+ futureReply!!.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+ }
+
+ override fun start(): Boolean {
+ // Ignore the fact start() could return false or throw an exception.
+ handler.post({ super.start() })
+ handler.waitForIdle(TIMEOUT_MS)
+ return true
+ }
+
+ override fun stop() {
+ handler.post({ super.stop() })
+ handler.waitForIdle(TIMEOUT_MS)
+ }
}
@get:Rule
val ignoreRule = DevSdkIgnoreRule()
- private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
private val pm by lazy { context.packageManager }
- private val powerManager by lazy { context.getSystemService(PowerManager::class.java)!! }
- private val wakeLock by lazy { powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG) }
+ private lateinit var network: Network
private lateinit var ifname: String
private lateinit var networkCallback: TestableNetworkCallback
private lateinit var caps: ApfCapabilities
+ private val handlerThread = HandlerThread("$TAG handler thread").apply { start() }
+ private val handler = Handler(handlerThread.looper)
+ private lateinit var packetReader: Icmp6PacketReader
fun getApfCapabilities(): ApfCapabilities {
val caps = runShellCommand("cmd network_stack apf $ifname capabilities").trim()
@@ -107,36 +244,9 @@
return ApfCapabilities(version, maxLen, packetFormat)
}
- fun pollingCheck(condition: () -> Boolean, timeout_ms: Int): Boolean {
- var polling_time = 0
- do {
- Thread.sleep(POLLING_INTERVAL_MS.toLong())
- polling_time += POLLING_INTERVAL_MS
- if (condition()) return true
- } while (polling_time < timeout_ms)
- return false
- }
-
- fun turnScreenOff() {
- if (!wakeLock.isHeld()) wakeLock.acquire()
- runShellCommandOrThrow("input keyevent KEYCODE_SLEEP")
- val result = pollingCheck({ !powerManager.isInteractive() }, timeout_ms = 2000)
- assertThat(result).isTrue()
- }
-
- fun turnScreenOn() {
- if (wakeLock.isHeld()) wakeLock.release()
- runShellCommandOrThrow("input keyevent KEYCODE_WAKEUP")
- val result = pollingCheck({ powerManager.isInteractive() }, timeout_ms = 2000)
- assertThat(result).isTrue()
- }
-
@Before
fun setUp() {
assume().that(pm.hasSystemFeature(FEATURE_WIFI)).isTrue()
- // APF must run when the screen is off and the device is not interactive.
- // TODO: consider running some of the tests with screen on (capabilities, read / write).
- turnScreenOff()
networkCallback = TestableNetworkCallback()
cm.requestNetwork(
@@ -146,6 +256,7 @@
.build(),
networkCallback
)
+ network = networkCallback.expect<Available>().network
networkCallback.eventuallyExpect<LinkPropertiesChanged>(TIMEOUT_MS) {
ifname = assertNotNull(it.lp.interfaceName)
true
@@ -155,17 +266,25 @@
// respective VSR releases and all other tests are based on the capabilities indicated.
runShellCommand("cmd network_stack apf $ifname pause")
caps = getApfCapabilities()
+
+ packetReader = Icmp6PacketReader(handler, network)
+ packetReader.start()
}
@After
fun tearDown() {
+ if (::packetReader.isInitialized) {
+ packetReader.stop()
+ }
+ handlerThread.quitSafely()
+ handlerThread.join()
+
if (::ifname.isInitialized) {
runShellCommand("cmd network_stack apf $ifname resume")
}
if (::networkCallback.isInitialized) {
cm.unregisterNetworkCallback(networkCallback)
}
- turnScreenOn()
}
@Test
@@ -203,7 +322,7 @@
}
fun installProgram(bytes: ByteArray) {
- val prog = HexDump.toHexString(bytes, 0 /* offset */, bytes.size, false /* upperCase */)
+ val prog = bytes.toHexString()
val result = runShellCommandOrThrow("cmd network_stack apf $ifname install $prog").trim()
// runShellCommandOrThrow only throws on S+.
assertThat(result).isEqualTo("success")
@@ -236,4 +355,14 @@
assertWithMessage("read/write $i byte prog failed").that(readResult).isEqualTo(program)
}
}
+
+ // TODO: this is a placeholder test to test the IcmpPacketReader functionality and will soon be
+ // replaced by a real test.
+ @Test
+ fun testPing() {
+ val data = ByteArray(56)
+ Random.nextBytes(data)
+ packetReader.sendPing(data)
+ assertThat(packetReader.expectPingReply()).isEqualTo(data)
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index d052551..6fa2812 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -905,13 +905,17 @@
val iface = createInterface()
val listener = EthernetStateListener()
addInterfaceStateListener(listener)
+ // Uses eventuallyExpect to account for interfaces that could already exist on device
+ listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
+
+ disableInterface(iface).expectResult(iface.name)
+ listener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+
+ enableInterface(iface).expectResult(iface.name)
listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
disableInterface(iface).expectResult(iface.name)
listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
-
- enableInterface(iface).expectResult(iface.name)
- listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
}
@Test
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index 73f65e0..06a827b 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -265,6 +265,14 @@
* Get all testable Networks with internet capability.
*/
private Set<Network> getTestableNetworks() throws InterruptedException {
+ // Calling requestNetwork() to request a cell or Wi-Fi network via CtsNetUtils or
+ // NetworkCallbackRule requires the CHANGE_NETWORK_STATE permission. This permission cannot
+ // be granted to instant apps. Therefore, return currently available testable networks
+ // directly in instant mode.
+ if (mContext.getApplicationInfo().isInstantApp()) {
+ return new ArraySet<>(mCtsNetUtils.getTestableNetworks());
+ }
+
// Obtain cell and Wi-Fi through CtsNetUtils (which uses NetworkCallbacks), as they may have
// just been reconnected by the test using NetworkCallbacks, so synchronous calls may not
// yet return them (synchronous calls and callbacks should not be mixed for a given
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 59e7d22..aee40c8 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -460,6 +460,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.lang.reflect.Method;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -19194,6 +19195,25 @@
verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
}
- // Note : adding tests is ConnectivityServiceTest is deprecated, as it is too big for
+ private static final int EXPECTED_TEST_METHOD_COUNT = 332;
+
+ @Test
+ public void testTestMethodCount() {
+ final Class<?> testClass = this.getClass();
+
+ int actualTestMethodCount = 0;
+ for (final Method method : testClass.getDeclaredMethods()) {
+ if (method.isAnnotationPresent(Test.class)) {
+ actualTestMethodCount++;
+ }
+ }
+
+ assertEquals("Adding tests in ConnectivityServiceTest is deprecated, "
+ + "as it is too big for maintenance. Please consider adding new tests "
+ + "in subclasses of CSTest instead.",
+ EXPECTED_TEST_METHOD_COUNT, actualTestMethodCount);
+ }
+
+ // Note : adding tests in ConnectivityServiceTest is deprecated, as it is too big for
// maintenance. Please consider adding new tests in subclasses of CSTest instead.
}
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 5731d01..aece3f7 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -41,6 +41,7 @@
import static com.android.server.NsdService.DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF;
import static com.android.server.NsdService.MdnsListener;
import static com.android.server.NsdService.NO_TRANSACTION;
+import static com.android.server.NsdService.checkHostname;
import static com.android.server.NsdService.parseTypeAndSubtype;
import static com.android.testutils.ContextUtils.mockService;
@@ -53,6 +54,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
@@ -1726,6 +1728,36 @@
}
@Test
+ public void TestCheckHostname() {
+ // Valid cases
+ assertTrue(checkHostname(null));
+ assertTrue(checkHostname("a"));
+ assertTrue(checkHostname("1"));
+ assertTrue(checkHostname("a-1234-bbbb-cccc000"));
+ assertTrue(checkHostname("A-1234-BBbb-CCCC000"));
+ assertTrue(checkHostname("1234-bbbb-cccc000"));
+ assertTrue(checkHostname("0123456789abcdef"
+ + "0123456789abcdef"
+ + "0123456789abcdef"
+ + "0123456789abcde" // 63 characters
+ ));
+
+ // Invalid cases
+ assertFalse(checkHostname("?"));
+ assertFalse(checkHostname("/"));
+ assertFalse(checkHostname("a-"));
+ assertFalse(checkHostname("B-"));
+ assertFalse(checkHostname("-A"));
+ assertFalse(checkHostname("-b"));
+ assertFalse(checkHostname("-1-"));
+ assertFalse(checkHostname("0123456789abcdef"
+ + "0123456789abcdef"
+ + "0123456789abcdef"
+ + "0123456789abcdef" // 64 characters
+ ));
+ }
+
+ @Test
@EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
public void testEnablePlatformMdnsBackend() {
final NsdManager client = connectClient(mService);
diff --git a/tests/unit/java/com/android/server/connectivity/MulticastRoutingCoordinatorServiceTest.kt b/tests/unit/java/com/android/server/connectivity/MulticastRoutingCoordinatorServiceTest.kt
index 6c2c256..5c994f5 100644
--- a/tests/unit/java/com/android/server/connectivity/MulticastRoutingCoordinatorServiceTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/MulticastRoutingCoordinatorServiceTest.kt
@@ -402,15 +402,18 @@
mService.getVirtualInterfaceIndex(mIfName1), oifsUpdate)
val mf6cctlDel = createStructMf6cctl(mSourceAddress, mGroupAddressScope5,
mService.getVirtualInterfaceIndex(mIfName1), mEmptyOifs)
+ val ifName1Copy = String(mIfName1.toCharArray())
+ val ifName2Copy = String(mIfName2.toCharArray())
+ val ifName3Copy = String(mIfName3.toCharArray())
verify(mDeps).setsockoptMrt6AddMfc(eq(mFd), eq(mf6cctlAdd))
- applyMulticastForwardNone(mIfName1, mIfName2)
+ applyMulticastForwardNone(ifName1Copy, ifName2Copy)
mLooper.dispatchAll()
verify(mDeps).setsockoptMrt6AddMfc(eq(mFd), eq(mf6cctlUpdate))
- applyMulticastForwardNone(mIfName1, mIfName3)
+ applyMulticastForwardNone(ifName1Copy, ifName3Copy)
mLooper.dispatchAll()
verify(mDeps, timeout(TIMEOUT_MS).times(1)).setsockoptMrt6DelMfc(eq(mFd), eq(mf6cctlDel))
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
index 271cc65..f7e0b0e 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -24,7 +24,6 @@
import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_HOST
import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE
-import com.android.server.connectivity.mdns.MdnsProber.ProbingInfo
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_A
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_AAAA
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_PTR
@@ -52,10 +51,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
private const val TEST_SERVICE_ID_1 = 42
private const val TEST_SERVICE_ID_2 = 43
@@ -132,10 +127,23 @@
private val deps = object : Dependencies() {
override fun getInterfaceInetAddresses(iface: NetworkInterface) =
Collections.enumeration(TEST_ADDRESSES.map { it.address })
+
+ override fun elapsedRealTime() = now
+
+ fun elapse(duration: Long) {
+ now += duration
+ }
+
+ fun resetElapsedRealTime() {
+ now = 100
+ }
+
+ var now: Long = 100
}
@Before
fun setUp() {
+ deps.resetElapsedRealTime();
thread.start()
}
@@ -1003,6 +1011,102 @@
}
@Test
+ fun testGetReply_ipv4AndIpv6Queries_ipv4AndIpv6Replies() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
+ val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+
+ val srcIpv4 = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+ val replyIpv4 = repository.getReply(query, srcIpv4)
+ val srcIpv6 = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
+ val replyIpv6 = repository.getReply(query, srcIpv6)
+
+ assertNotNull(replyIpv4)
+ assertEquals(MdnsConstants.getMdnsIPv4Address(), replyIpv4.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, replyIpv4.destination.port)
+ assertNotNull(replyIpv6)
+ assertEquals(MdnsConstants.getMdnsIPv6Address(), replyIpv6.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, replyIpv6.destination.port)
+ }
+
+ @Test
+ fun testGetReply_twoIpv4QueriesInOneSecond_theSecondReplyIsThrottled() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
+ val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+
+ val srcIpv4 = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+ val firstReplyIpv4 = repository.getReply(query, srcIpv4)
+ deps.elapse(500L)
+ val secondReply = repository.getReply(query, srcIpv4)
+
+ assertNotNull(firstReplyIpv4)
+ assertEquals(MdnsConstants.getMdnsIPv4Address(), firstReplyIpv4.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, firstReplyIpv4.destination.port)
+ assertNull(secondReply)
+ }
+
+
+ @Test
+ fun testGetReply_twoIpv6QueriesInOneSecond_theSecondReplyIsThrottled() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
+ val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+
+ val srcIpv6 = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
+ val firstReplyIpv6 = repository.getReply(query, srcIpv6)
+ deps.elapse(500L)
+ val secondReply = repository.getReply(query, srcIpv6)
+
+ assertNotNull(firstReplyIpv6)
+ assertEquals(MdnsConstants.getMdnsIPv6Address(), firstReplyIpv6.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, firstReplyIpv6.destination.port)
+ assertNull(secondReply)
+ }
+
+ @Test
+ fun testGetReply_twoIpv4QueriesInMoreThanOneSecond_repliesAreNotThrottled() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
+ val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+
+ val srcIpv4 = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+ val firstReplyIpv4 = repository.getReply(query, srcIpv4)
+ // The longest possible interval that may make the reply throttled is
+ // 1000 (MIN_MULTICAST_REPLY_INTERVAL_MS) + 120 (delay for shared name) = 1120
+ deps.elapse(1121L)
+ val secondReplyIpv4 = repository.getReply(query, srcIpv4)
+
+ assertNotNull(firstReplyIpv4)
+ assertEquals(MdnsConstants.getMdnsIPv4Address(), firstReplyIpv4.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, firstReplyIpv4.destination.port)
+ assertNotNull(secondReplyIpv4)
+ assertEquals(MdnsConstants.getMdnsIPv4Address(), secondReplyIpv4.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, secondReplyIpv4.destination.port)
+ }
+
+ @Test
+ fun testGetReply_twoIpv6QueriesInMoreThanOneSecond_repliesAreNotThrottled() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
+ val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+
+ val srcIpv6 = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
+ val firstReplyIpv6 = repository.getReply(query, srcIpv6)
+ // The longest possible interval that may make the reply throttled is
+ // 1000 (MIN_MULTICAST_REPLY_INTERVAL_MS) + 120 (delay for shared name) = 1120
+ deps.elapse(1121L)
+ val secondReplyIpv6 = repository.getReply(query, srcIpv6)
+
+ assertNotNull(firstReplyIpv6)
+ assertEquals(MdnsConstants.getMdnsIPv6Address(), firstReplyIpv6.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, firstReplyIpv6.destination.port)
+ assertNotNull(secondReplyIpv6)
+ assertEquals(MdnsConstants.getMdnsIPv6Address(), secondReplyIpv6.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, secondReplyIpv6.destination.port)
+ }
+
+ @Test
fun testGetConflictingServices() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)