Snap for 12053972 from 70a9b5806930481969c3ead42d26113bce0372b8 to mainline-media-release Change-Id: If5bd9f1d30635d7211f570093e853af4668019e2
diff --git a/Android.bp b/Android.bp index 3712ba6..ab24060 100644 --- a/Android.bp +++ b/Android.bp
@@ -69,6 +69,7 @@ "framework-connectivity", "framework-connectivity-t", "framework-statsd", + "framework-tethering", "framework-wifi", ], } @@ -90,6 +91,7 @@ "framework-connectivity.stubs.module_lib", "framework-connectivity-t", "framework-statsd", + "framework-tethering", "framework-wifi", ], }
diff --git a/src/android/net/apf/AndroidPacketFilter.java b/src/android/net/apf/AndroidPacketFilter.java index 720de03..6dd4fad 100644 --- a/src/android/net/apf/AndroidPacketFilter.java +++ b/src/android/net/apf/AndroidPacketFilter.java
@@ -106,4 +106,11 @@ default boolean hasDataAccess(int apfVersionSupported) { return apfVersionSupported > 2; } + + /** + * Whether the ApfFilter supports generating ND offload code. + */ + default boolean supportNdOffload() { + return false; + } }
diff --git a/src/android/net/apf/ApfCounterTracker.java b/src/android/net/apf/ApfCounterTracker.java index f5416c5..6c5b566 100644 --- a/src/android/net/apf/ApfCounterTracker.java +++ b/src/android/net/apf/ApfCounterTracker.java
@@ -50,6 +50,7 @@ APF_VERSION, APF_PROGRAM_ID, // TODO: removing PASSED_ARP after remove LegacyApfFilter.java + // The counter sequence should keep the same as ApfSessionInfoMetrics.java PASSED_ARP, // see also MIN_PASS_COUNTER below. PASSED_ARP_BROADCAST_REPLY, // TODO: removing PASSED_ARP_NON_IPV4 after remove LegacyApfFilter.java
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index 92c25e7..f6c6ac7 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java
@@ -2250,8 +2250,7 @@ protected ApfV4GeneratorBase<?> emitPrologueLocked() throws IllegalInstructionException { // This is guaranteed to succeed because of the check in maybeCreate. ApfV4GeneratorBase<?> gen; - if (SdkLevel.isAtLeastV() - && ApfV6Generator.supportsVersion(mApfVersionSupported)) { + if (shouldUseApfV6Generator()) { gen = new ApfV6Generator(mApfVersionSupported, mApfRamSize, mInstallableProgramSizeClamp); } else { @@ -2688,6 +2687,15 @@ installNewProgramLocked(); } + @Override + public boolean supportNdOffload() { + return shouldUseApfV6Generator() && mShouldHandleNdOffload; + } + + private boolean shouldUseApfV6Generator() { + return SdkLevel.isAtLeastV() && ApfV6Generator.supportsVersion(mApfVersionSupported); + } + /** * Add TCP keepalive ack packet filter. * This will add a filter to drop acks to the keepalive packet passed as an argument.
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java index 7b9cd73..223ed75 100644 --- a/src/android/net/ip/IpClient.java +++ b/src/android/net/ip/IpClient.java
@@ -1023,10 +1023,15 @@ public void onClatInterfaceStateUpdate(boolean add) { getHandler().post(() -> { if (mHasSeenClatInterface == add) return; - // Clat interface information is spliced into LinkProperties by - // ConnectivityService, so it cannot be added to the LinkProperties - // here as those propagate back to ConnectivityService. - mCallback.setNeighborDiscoveryOffload(add ? false : true); + // If Apf is not supported or Apf doesn't support ND offload, then + // configure the vendor ND offload feature based on the Clat + // interface state. + if (mApfFilter == null || !mApfFilter.supportNdOffload()) { + // Clat interface information is spliced into LinkProperties by + // ConnectivityService, so it cannot be added to the LinkProperties + // here as those propagate back to ConnectivityService. + mCallback.setNeighborDiscoveryOffload(add ? false : true); + } mHasSeenClatInterface = add; if (mApfFilter != null) { mApfFilter.updateClatInterfaceState(add); @@ -3104,6 +3109,10 @@ // at the beginning. mHasSeenClatInterface = false; mApfFilter = maybeCreateApfFilter(mCurrentApfCapabilities); + // If Apf supports ND offload, then turn off the vendor ND offload feature. + if (mApfFilter != null && mApfFilter.supportNdOffload()) { + mCallback.setNeighborDiscoveryOffload(false); + } // TODO: investigate the effects of any multicast filtering racing/interfering with the // rest of this IP configuration startup. if (mApfFilter == null) { @@ -3561,6 +3570,10 @@ final ApfCapabilities apfCapabilities = (ApfCapabilities) msg.obj; if (handleUpdateApfCapabilities(apfCapabilities)) { mApfFilter = maybeCreateApfFilter(apfCapabilities); + // If Apf supports ND offload, then turn off the vendor ND offload feature. + if (mApfFilter != null && mApfFilter.supportNdOffload()) { + mCallback.setNeighborDiscoveryOffload(false); + } } break;
diff --git a/src/android/net/util/RawSocketUtils.java b/src/android/net/util/RawSocketUtils.java new file mode 100644 index 0000000..a6c8a40 --- /dev/null +++ b/src/android/net/util/RawSocketUtils.java
@@ -0,0 +1,115 @@ +/* + * 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.util; + +import static android.Manifest.permission.NETWORK_SETTINGS; +import static android.system.OsConstants.AF_PACKET; +import static android.system.OsConstants.SOCK_NONBLOCK; +import static android.system.OsConstants.SOCK_RAW; + +import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; +import static com.android.net.module.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_LENGTH; +import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_OFFSET; + +import android.annotation.RequiresPermission; +import android.content.Context; +import android.net.TetheringManager; +import android.system.Os; + +import androidx.annotation.NonNull; + +import com.android.internal.util.HexDump; + +import java.io.FileDescriptor; +import java.net.NetworkInterface; +import java.net.SocketAddress; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class RawSocketUtils { + // For isTetheredInterface, a quick Tethering event callback is expected + // since there's no start/stop Tethering involved. This timeout allows + // system messages to be handled, preventing flaky test results. + private static final int TETHERING_EVENT_CALLBACK_TIMEOUT_MS = 3000; + + /** + * Send a raw packet represents in Hex format to the downstream interface. + * <p> + * Note that the target interface is limited to tethering downstream + * for security considerations. + */ + @RequiresPermission(NETWORK_SETTINGS) + public static void sendRawPacketDownStream(@NonNull Context context, @NonNull String ifaceName, + @NonNull String packetInHex) throws Exception { + // 1. Verify Tethering Downstream Interface. + enforceTetheredInterface(context, ifaceName); + + // 2. Hex to Byte Array Conversion + final byte[] packetData = HexDump.hexStringToByteArray(packetInHex); + final byte[] destMac = Arrays.copyOfRange(packetData, ETHER_DST_ADDR_OFFSET, + ETHER_DST_ADDR_OFFSET + ETHER_ADDR_LEN); + final byte[] etherTypeBytes = Arrays.copyOfRange(packetData, ETHER_TYPE_OFFSET, + ETHER_TYPE_OFFSET + ETHER_TYPE_LENGTH); + final int etherType = ((etherTypeBytes[0] & 0xFF) << 8) | (etherTypeBytes[1] & 0xFF); + + // 3. Obtain Network Interface + final NetworkInterface iface = NetworkInterface.getByName(ifaceName); + if (iface == null) { + throw new IllegalArgumentException("Invalid network interface: " + ifaceName); + } + + // 4. Construct and Send Packet. + final SocketAddress addr = SocketUtils.makePacketSocketAddress( + etherType, + iface.getIndex(), + destMac + ); + final FileDescriptor sock = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0); + try { + Os.sendto(sock, packetData, 0, packetData.length, 0, addr); + } finally { + SocketUtils.closeSocket(sock); + } + } + + @RequiresPermission(NETWORK_SETTINGS) + private static void enforceTetheredInterface(@NonNull Context context, + @NonNull String interfaceName) + throws ExecutionException, InterruptedException, TimeoutException { + final TetheringManager tm = context.getSystemService(TetheringManager.class); + final CompletableFuture<List<String>> tetheredInterfaces = new CompletableFuture<>(); + final TetheringManager.TetheringEventCallback callback = + new TetheringManager.TetheringEventCallback() { + @Override + public void onTetheredInterfacesChanged(@NonNull List<String> interfaces) { + tetheredInterfaces.complete(interfaces); + } + }; + tm.registerTetheringEventCallback(c -> c.run() /* executor */, callback); + final List<String> tetheredIfaces = tetheredInterfaces.get( + TETHERING_EVENT_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS); + if (!tetheredIfaces.contains(interfaceName)) { + throw new SecurityException("Only tethered interfaces " + tetheredIfaces + + " are expected, but got " + interfaceName); + } + } +}
diff --git a/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java b/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java index d1776c9..c2c51f6 100644 --- a/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java +++ b/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java
@@ -16,11 +16,115 @@ package com.android.networkstack.metrics; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_802_3_FRAME; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_NON_IPV4; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_OTHER_HOST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REPLY_SPA_NO_HOST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REQUEST_ANYHOST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REQUEST_REPLIED; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_UNKNOWN; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_V6_ONLY; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHERTYPE_NOT_ALLOWED; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_ETH_BROADCAST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_GARP_REPLY; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_BROADCAST_ADDR; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_BROADCAST_NET; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_KEEPALIVE_ACK; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_L2_BROADCAST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_MULTICAST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NATT_KEEPALIVE; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NON_DHCP4; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_TCP_PORT7_UNICAST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_KEEPALIVE_ACK; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_NA; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_PING; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NON_ICMP_MULTICAST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_OTHER_HOST; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ROUTER_SOLICITATION; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_MDNS; +import static android.net.apf.ApfCounterTracker.Counter.DROPPED_RA; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_BROADCAST_REPLY; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_NON_IPV4; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_REQUEST; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_UNICAST_REPLY; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_UNKNOWN; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_DHCP; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_FROM_DHCPV4_SERVER; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_UNICAST; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NON_ICMP; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_DAD; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_ADDRESS; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_SLLA_OPTION; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_TENTATIVE; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_UNICAST_NON_ICMP; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_MDNS; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_MLD; +import static android.net.apf.ApfCounterTracker.Counter.PASSED_NON_IP_UNICAST; +import static android.net.apf.ApfCounterTracker.Counter.TOTAL_PACKETS; +import static android.stats.connectivity.CounterName.CN_DROPPED_802_3_FRAME; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_NON_IPV4; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_OTHER_HOST; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_REPLY_SPA_NO_HOST; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_REQUEST_ANYHOST; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_REQUEST_REPLIED; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_UNKNOWN; +import static android.stats.connectivity.CounterName.CN_DROPPED_ARP_V6_ONLY; +import static android.stats.connectivity.CounterName.CN_DROPPED_ETHERTYPE_NOT_ALLOWED; +import static android.stats.connectivity.CounterName.CN_DROPPED_ETH_BROADCAST; +import static android.stats.connectivity.CounterName.CN_DROPPED_GARP_REPLY; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_BROADCAST_ADDR; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_BROADCAST_NET; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_KEEPALIVE_ACK; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_L2_BROADCAST; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_MULTICAST; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_NATT_KEEPALIVE; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_NON_DHCP4; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_KEEPALIVE_ACK; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MULTICAST; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MULTICAST_NA; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MULTICAST_PING; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_NON_ICMP_MULTICAST; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_NS_INVALID; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_NS_OTHER_HOST; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_NS_REPLIED_NON_DAD; +import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_ROUTER_SOLICITATION; +import static android.stats.connectivity.CounterName.CN_DROPPED_MDNS; +import static android.stats.connectivity.CounterName.CN_DROPPED_RA; +import static android.stats.connectivity.CounterName.CN_PASSED_ARP; +import static android.stats.connectivity.CounterName.CN_PASSED_ARP_BROADCAST_REPLY; +import static android.stats.connectivity.CounterName.CN_PASSED_ARP_REQUEST; +import static android.stats.connectivity.CounterName.CN_PASSED_ARP_UNICAST_REPLY; +import static android.stats.connectivity.CounterName.CN_PASSED_DHCP; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV4; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV4_FROM_DHCPV4_SERVER; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV4_UNICAST; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_ICMP; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NON_ICMP; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NS_DAD; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NS_NO_ADDRESS; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NS_NO_SLLA_OPTION; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_NS_TENTATIVE; +import static android.stats.connectivity.CounterName.CN_PASSED_IPV6_UNICAST_NON_ICMP; +import static android.stats.connectivity.CounterName.CN_PASSED_MDNS; +import static android.stats.connectivity.CounterName.CN_PASSED_MLD; +import static android.stats.connectivity.CounterName.CN_PASSED_NON_IP_UNICAST; +import static android.stats.connectivity.CounterName.CN_TOTAL_PACKETS; +import static android.stats.connectivity.CounterName.CN_UNKNOWN; + import android.net.apf.ApfCounterTracker.Counter; import android.stats.connectivity.CounterName; import androidx.annotation.VisibleForTesting; +import java.util.EnumMap; +import java.util.Map; + /** * Class to record the network stack ApfSessionInfo metrics into statsd. * @@ -31,6 +135,65 @@ public class ApfSessionInfoMetrics { // Define the maximum size of the counter list public static final int MAX_NUM_OF_COUNTERS = Counter.class.getEnumConstants().length - 1; + private static final EnumMap<Counter, CounterName> apfCounterMetricsMap = new EnumMap<>( + Map.ofEntries( + Map.entry(TOTAL_PACKETS, CN_TOTAL_PACKETS), + // The counter sequence should be keep the same in ApfCounterTracker.java + Map.entry(PASSED_ARP, CN_PASSED_ARP), + Map.entry(PASSED_ARP_BROADCAST_REPLY, CN_PASSED_ARP_BROADCAST_REPLY), + // deprecated in ApfFilter, PASSED_ARP_NON_IPV4 ==> DROPPED_ARP_NON_IPV4 + Map.entry(PASSED_ARP_NON_IPV4, CN_UNKNOWN), + Map.entry(PASSED_ARP_REQUEST, CN_PASSED_ARP_REQUEST), + Map.entry(PASSED_ARP_UNICAST_REPLY, CN_PASSED_ARP_UNICAST_REPLY), + // deprecated in ApfFilter, PASSED_ARP_UNKNOWN ==> DROPPED_ARP_UNKNOWN + Map.entry(PASSED_ARP_UNKNOWN, CN_UNKNOWN), + Map.entry(PASSED_DHCP, CN_PASSED_DHCP), + Map.entry(PASSED_IPV4, CN_PASSED_IPV4), + Map.entry(PASSED_IPV4_FROM_DHCPV4_SERVER, CN_PASSED_IPV4_FROM_DHCPV4_SERVER), + Map.entry(PASSED_IPV4_UNICAST, CN_PASSED_IPV4_UNICAST), + Map.entry(PASSED_IPV6_ICMP, CN_PASSED_IPV6_ICMP), + Map.entry(PASSED_IPV6_NON_ICMP, CN_PASSED_IPV6_NON_ICMP), + Map.entry(PASSED_IPV6_NS_DAD, CN_PASSED_IPV6_NS_DAD), + Map.entry(PASSED_IPV6_NS_NO_ADDRESS, CN_PASSED_IPV6_NS_NO_ADDRESS), + Map.entry(PASSED_IPV6_NS_NO_SLLA_OPTION, CN_PASSED_IPV6_NS_NO_SLLA_OPTION), + Map.entry(PASSED_IPV6_NS_TENTATIVE, CN_PASSED_IPV6_NS_TENTATIVE), + Map.entry(PASSED_IPV6_UNICAST_NON_ICMP, CN_PASSED_IPV6_UNICAST_NON_ICMP), + Map.entry(PASSED_NON_IP_UNICAST, CN_PASSED_NON_IP_UNICAST), + Map.entry(PASSED_MDNS, CN_PASSED_MDNS), + Map.entry(PASSED_MLD, CN_PASSED_MLD), + Map.entry(DROPPED_ETH_BROADCAST, CN_DROPPED_ETH_BROADCAST), + Map.entry(DROPPED_RA, CN_DROPPED_RA), + Map.entry(DROPPED_IPV4_L2_BROADCAST, CN_DROPPED_IPV4_L2_BROADCAST), + Map.entry(DROPPED_IPV4_BROADCAST_ADDR, CN_DROPPED_IPV4_BROADCAST_ADDR), + Map.entry(DROPPED_IPV4_BROADCAST_NET, CN_DROPPED_IPV4_BROADCAST_NET), + Map.entry(DROPPED_IPV4_MULTICAST, CN_DROPPED_IPV4_MULTICAST), + Map.entry(DROPPED_IPV4_NON_DHCP4, CN_DROPPED_IPV4_NON_DHCP4), + Map.entry(DROPPED_IPV6_ROUTER_SOLICITATION, CN_DROPPED_IPV6_ROUTER_SOLICITATION), + Map.entry(DROPPED_IPV6_MULTICAST_NA, CN_DROPPED_IPV6_MULTICAST_NA), + Map.entry(DROPPED_IPV6_MULTICAST, CN_DROPPED_IPV6_MULTICAST), + Map.entry(DROPPED_IPV6_MULTICAST_PING, CN_DROPPED_IPV6_MULTICAST_PING), + Map.entry(DROPPED_IPV6_NON_ICMP_MULTICAST, CN_DROPPED_IPV6_NON_ICMP_MULTICAST), + Map.entry(DROPPED_IPV6_NS_INVALID, CN_DROPPED_IPV6_NS_INVALID), + Map.entry(DROPPED_IPV6_NS_OTHER_HOST, CN_DROPPED_IPV6_NS_OTHER_HOST), + Map.entry(DROPPED_IPV6_NS_REPLIED_NON_DAD, CN_DROPPED_IPV6_NS_REPLIED_NON_DAD), + Map.entry(DROPPED_802_3_FRAME, CN_DROPPED_802_3_FRAME), + Map.entry(DROPPED_ETHERTYPE_NOT_ALLOWED, CN_DROPPED_ETHERTYPE_NOT_ALLOWED), + Map.entry(DROPPED_IPV4_KEEPALIVE_ACK, CN_DROPPED_IPV4_KEEPALIVE_ACK), + Map.entry(DROPPED_IPV6_KEEPALIVE_ACK, CN_DROPPED_IPV6_KEEPALIVE_ACK), + Map.entry(DROPPED_IPV4_NATT_KEEPALIVE, CN_DROPPED_IPV4_NATT_KEEPALIVE), + Map.entry(DROPPED_MDNS, CN_DROPPED_MDNS), + // TODO: Not supported yet in the metrics backend. + Map.entry(DROPPED_IPV4_TCP_PORT7_UNICAST, CN_UNKNOWN), + Map.entry(DROPPED_ARP_NON_IPV4, CN_DROPPED_ARP_NON_IPV4), + Map.entry(DROPPED_ARP_OTHER_HOST, CN_DROPPED_ARP_OTHER_HOST), + Map.entry(DROPPED_ARP_REPLY_SPA_NO_HOST, CN_DROPPED_ARP_REPLY_SPA_NO_HOST), + Map.entry(DROPPED_ARP_REQUEST_ANYHOST, CN_DROPPED_ARP_REQUEST_ANYHOST), + Map.entry(DROPPED_ARP_REQUEST_REPLIED, CN_DROPPED_ARP_REQUEST_REPLIED), + Map.entry(DROPPED_ARP_UNKNOWN, CN_DROPPED_ARP_UNKNOWN), + Map.entry(DROPPED_ARP_V6_ONLY, CN_DROPPED_ARP_V6_ONLY), + Map.entry(DROPPED_GARP_REPLY, CN_DROPPED_GARP_REPLY) + ) + ); private final ApfSessionInfoReported.Builder mStatsBuilder = ApfSessionInfoReported.newBuilder(); private final ApfCounterList.Builder mApfCounterListBuilder = ApfCounterList.newBuilder(); @@ -104,85 +267,6 @@ */ @VisibleForTesting public static CounterName apfFilterCounterToEnum(final Counter counter) { - switch(counter) { - case TOTAL_PACKETS: - return CounterName.CN_TOTAL_PACKETS; - case PASSED_ARP: - return CounterName.CN_PASSED_ARP; - case PASSED_DHCP: - return CounterName.CN_PASSED_DHCP; - case PASSED_IPV4: - return CounterName.CN_PASSED_IPV4; - case PASSED_IPV6_NON_ICMP: - return CounterName.CN_PASSED_IPV6_NON_ICMP; - case PASSED_IPV4_UNICAST: - return CounterName.CN_PASSED_IPV4_UNICAST; - case PASSED_IPV6_ICMP: - return CounterName.CN_PASSED_IPV6_ICMP; - case PASSED_IPV6_UNICAST_NON_ICMP: - return CounterName.CN_PASSED_IPV6_UNICAST_NON_ICMP; - // PASSED_ARP_NON_IPV4 and PASSED_ARP_UNKNOWN were deprecated in ApfFilter: - // PASSED_ARP_NON_IPV4 ==> DROPPED_ARP_NON_IPV4 - // PASSED_ARP_UNKNOWN ==> DROPPED_ARP_UNKNOWN - // They are not supported in the metrics. - case PASSED_ARP_NON_IPV4: - case PASSED_ARP_UNKNOWN: - return CounterName.CN_UNKNOWN; - case PASSED_ARP_UNICAST_REPLY: - return CounterName.CN_PASSED_ARP_UNICAST_REPLY; - case PASSED_NON_IP_UNICAST: - return CounterName.CN_PASSED_NON_IP_UNICAST; - case PASSED_MDNS: - return CounterName.CN_PASSED_MDNS; - case DROPPED_ETH_BROADCAST: - return CounterName.CN_DROPPED_ETH_BROADCAST; - case DROPPED_RA: - return CounterName.CN_DROPPED_RA; - case DROPPED_GARP_REPLY: - return CounterName.CN_DROPPED_GARP_REPLY; - case DROPPED_ARP_OTHER_HOST: - return CounterName.CN_DROPPED_ARP_OTHER_HOST; - case DROPPED_IPV4_L2_BROADCAST: - return CounterName.CN_DROPPED_IPV4_L2_BROADCAST; - case DROPPED_IPV4_BROADCAST_ADDR: - return CounterName.CN_DROPPED_IPV4_BROADCAST_ADDR; - case DROPPED_IPV4_BROADCAST_NET: - return CounterName.CN_DROPPED_IPV4_BROADCAST_NET; - case DROPPED_IPV4_MULTICAST: - return CounterName.CN_DROPPED_IPV4_MULTICAST; - case DROPPED_IPV6_ROUTER_SOLICITATION: - return CounterName.CN_DROPPED_IPV6_ROUTER_SOLICITATION; - case DROPPED_IPV6_MULTICAST_NA: - return CounterName.CN_DROPPED_IPV6_MULTICAST_NA; - case DROPPED_IPV6_MULTICAST: - return CounterName.CN_DROPPED_IPV6_MULTICAST; - case DROPPED_IPV6_MULTICAST_PING: - return CounterName.CN_DROPPED_IPV6_MULTICAST_PING; - case DROPPED_IPV6_NON_ICMP_MULTICAST: - return CounterName.CN_DROPPED_IPV6_NON_ICMP_MULTICAST; - case DROPPED_802_3_FRAME: - return CounterName.CN_DROPPED_802_3_FRAME; - case DROPPED_ETHERTYPE_NOT_ALLOWED: - return CounterName.CN_DROPPED_ETHERTYPE_DENYLISTED; - case DROPPED_ARP_REPLY_SPA_NO_HOST: - return CounterName.CN_DROPPED_ARP_REPLY_SPA_NO_HOST; - case DROPPED_IPV4_KEEPALIVE_ACK: - return CounterName.CN_DROPPED_IPV4_KEEPALIVE_ACK; - case DROPPED_IPV6_KEEPALIVE_ACK: - return CounterName.CN_DROPPED_IPV6_KEEPALIVE_ACK; - case DROPPED_IPV4_NATT_KEEPALIVE: - return CounterName.CN_DROPPED_IPV4_NATT_KEEPALIVE; - case DROPPED_MDNS: - return CounterName.CN_DROPPED_MDNS; - case DROPPED_IPV4_TCP_PORT7_UNICAST: - // TODO: Not supported yet in the metrics backend. - return CounterName.CN_UNKNOWN; - case DROPPED_ARP_NON_IPV4: - return CounterName.CN_DROPPED_ARP_NON_IPV4; - case DROPPED_ARP_UNKNOWN: - return CounterName.CN_DROPPED_ARP_UNKNOWN; - default: - return CounterName.CN_UNKNOWN; - } + return apfCounterMetricsMap.getOrDefault(counter, CN_UNKNOWN); } }
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java index a7563f4..33a8308 100755 --- a/src/com/android/networkstack/util/NetworkStackUtils.java +++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -274,6 +274,11 @@ public static final String IPCLIENT_DHCPV6_PD_PREFERRED_FLAG_VERSION = "ipclient_dhcpv6_pd_preferred_flag_version"; + /** + * Experiment flag to enable Discovery of Designated Resolvers (DDR). + */ + public static final String DNS_DDR_VERSION = "dns_ddr_version"; + /**** BEGIN Feature Kill Switch Flags ****/ /**
diff --git a/src/com/android/server/NetworkStackService.java b/src/com/android/server/NetworkStackService.java index aa8f3fa..4d10c3b 100644 --- a/src/com/android/server/NetworkStackService.java +++ b/src/com/android/server/NetworkStackService.java
@@ -19,6 +19,7 @@ import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR; +import static android.net.util.RawSocketUtils.sendRawPacketDownStream; import static com.android.net.module.util.DeviceConfigUtils.getResBooleanConfig; import static com.android.net.module.util.FeatureVersions.FEATURE_IS_UID_NETWORKING_BLOCKED; @@ -549,6 +550,23 @@ mContext.getSystemService(ConnectivityManager.class); pw.println(cm.isUidNetworkingBlocked(uid, metered /* isNetworkMetered */)); return 0; + case "send-raw-packet-downstream": { + // Usage : cmd network_stack send-raw-packet-downstream + // <interface> <packet-in-hex> + // If no argument, get and display the usage help. + if (getRemainingArgsCount() != 2) { + onHelp(); + throw new IllegalArgumentException("Incorrect number of arguments"); + } + final String iface = getNextArg(); + final String packetInHex = getNextArg(); + try { + sendRawPacketDownStream(mContext, iface, packetInHex); + } catch (Exception e) { + throw new RuntimeException(e); + } + return 0; + } case "apf": // Usage: cmd network_stack apf <iface> <cmd> final String iface = getNextArg(); @@ -585,6 +603,12 @@ pw.println(" Get whether the networking is blocked for given uid and metered."); pw.println(" <uid>: The target uid."); pw.println(" <metered>: [true|false], Whether the target network is metered."); + pw.println(" send-raw-packet-downstream <interface> <packet-in-hex>"); + pw.println(" Send raw packet for testing purpose."); + pw.println(" <interface>: Target interface name, note that this is limited"); + pw.println(" to tethering downstream for security considerations."); + pw.println(" <packet_in_hex>: A valid hexadecimal representation of "); + pw.println(" a packet starting from L2 header."); pw.println(" apf <iface> <cmd>"); pw.println(" APF utility commands for integration tests."); pw.println(" <iface>: the network interface the provided command operates on.");
diff --git a/src/com/android/server/connectivity/DdrTracker.java b/src/com/android/server/connectivity/DdrTracker.java new file mode 100644 index 0000000..af86914 --- /dev/null +++ b/src/com/android/server/connectivity/DdrTracker.java
@@ -0,0 +1,96 @@ +/* + * 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.connectivity; + +import static com.android.net.module.util.ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OFF; +import static com.android.net.module.util.ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OPPORTUNISTIC; +import static com.android.net.module.util.ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.net.shared.PrivateDnsConfig; +import android.text.TextUtils; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A class to perform DDR on a given network (to be implemented). + * + */ +class DdrTracker { + @IntDef(prefix = { "PRIVATE_DNS_MODE_" }, value = { + PRIVATE_DNS_MODE_OFF, + PRIVATE_DNS_MODE_OPPORTUNISTIC, + PRIVATE_DNS_MODE_PROVIDER_HOSTNAME + }) + @Retention(RetentionPolicy.SOURCE) + private @interface PrivateDnsMode {} + + // Stores the DNS information that is synced with current DNS configuration. + @NonNull + private DnsInfo mDnsInfo; + + DdrTracker() { + mDnsInfo = new DnsInfo(new PrivateDnsConfig(false /* useTls */)); + } + + /** + * If the private DNS settings on the network has changed, this function updates + * the DnsInfo and returns true; otherwise, the DnsInfo remains the same and this function + * returns false. + */ + boolean notifyPrivateDnsSettingsChanged(@NonNull PrivateDnsConfig cfg) { + if (arePrivateDnsSettingsEquals(cfg, mDnsInfo.cfg)) return false; + + mDnsInfo = new DnsInfo(cfg); + return true; + } + + @PrivateDnsMode int getPrivateDnsMode() { + return mDnsInfo.cfg.mode; + } + + // Returns a non-empty string (strict mode) or an empty string (off/opportunistic mode) . + @VisibleForTesting + @NonNull + String getStrictModeHostname() { + return mDnsInfo.cfg.hostname; + } + + @VisibleForTesting + private static boolean arePrivateDnsSettingsEquals(@NonNull PrivateDnsConfig a, + @NonNull PrivateDnsConfig b) { + return a.mode == b.mode && TextUtils.equals(a.hostname, b.hostname); + } + + /** + * A class to store current DNS configuration. Only the information relevant to DDR is stored. + * 1. Private DNS setting. + * 2. A list of Unencrypted DNS servers (to be implemented) + */ + private static class DnsInfo { + @NonNull + public final PrivateDnsConfig cfg; + + DnsInfo(@NonNull PrivateDnsConfig cfg) { + this.cfg = cfg; + } + } +}
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java index 895fc54..05103d9 100755 --- a/src/com/android/server/connectivity/NetworkMonitor.java +++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -462,6 +462,9 @@ @VisibleForTesting static final int MAX_PROBE_THREAD_POOL_SIZE = 5; private String mPrivateDnsProviderHostname = ""; + private final boolean mDdrEnabled; + @NonNull + private final DdrTracker mDdrTracker; private final Context mContext; private final INetworkMonitorCallbacks mCallback; @@ -679,6 +682,7 @@ context, NetworkStackUtils.REEVALUATE_WHEN_RESUME); mAsyncPrivdnsResolutionEnabled = deps.isFeatureEnabled(context, NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION); + mDdrEnabled = deps.isFeatureEnabled(context, NetworkStackUtils.DNS_DDR_VERSION); mUseHttps = getUseHttpsValidation(); mCaptivePortalUserAgent = getCaptivePortalUserAgent(); mCaptivePortalFallbackSpecs = @@ -714,6 +718,8 @@ mLinkProperties = new LinkProperties(); mNetworkCapabilities = new NetworkCapabilities(null); mNetworkAgentConfig = NetworkAgentConfigShimImpl.newInstance(null); + + mDdrTracker = new DdrTracker(); } /** @@ -1084,6 +1090,9 @@ case CMD_PRIVATE_DNS_SETTINGS_CHANGED: { final PrivateDnsConfig cfg = (PrivateDnsConfig) message.obj; final TcpSocketTracker tst = getTcpSocketTracker(); + if (mDdrEnabled) { + mDdrTracker.notifyPrivateDnsSettingsChanged(cfg); + } if (!isPrivateDnsValidationRequired() || !cfg.inStrictMode()) { // No DNS resolution required. //
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp index 91e94a8..e26ea28 100644 --- a/tests/unit/Android.bp +++ b/tests/unit/Android.bp
@@ -32,7 +32,7 @@ "kotlin-reflect", "mockito-target-extended-minus-junit4", "net-tests-utils", - //"net-utils-framework-common", + "net-utils-framework-common", "testables", ], libs: [
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java index 2a807b5..71f7ebf 100644 --- a/tests/unit/src/android/net/ip/IpClientTest.java +++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -16,14 +16,17 @@ package android.net.ip; +import static android.net.apf.BaseApfGenerator.APF_VERSION_6; import static android.net.ip.IpClientLinkObserver.CONFIG_SOCKET_RECV_BUFSIZE; import static android.net.ip.IpClientLinkObserver.SOCKET_RECV_BUFSIZE; +import static android.system.OsConstants.AF_UNSPEC; import static android.system.OsConstants.ARPHRD_ETHER; import static android.system.OsConstants.IFA_F_PERMANENT; import static android.system.OsConstants.IFA_F_TENTATIVE; import static android.system.OsConstants.RT_SCOPE_UNIVERSE; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK; import static com.android.net.module.util.netlink.NetlinkConstants.RTPROT_KERNEL; import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELROUTE; import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWADDR; @@ -47,9 +50,11 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -91,8 +96,10 @@ import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.netlink.NduseroptMessage; import com.android.net.module.util.netlink.RtNetlinkAddressMessage; +import com.android.net.module.util.netlink.RtNetlinkLinkMessage; import com.android.net.module.util.netlink.RtNetlinkRouteMessage; import com.android.net.module.util.netlink.StructIfaddrMsg; +import com.android.net.module.util.netlink.StructIfinfoMsg; import com.android.net.module.util.netlink.StructNdOptRdnss; import com.android.net.module.util.netlink.StructNlMsgHdr; import com.android.net.module.util.netlink.StructRtMsg; @@ -109,6 +116,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; @@ -139,6 +147,8 @@ private static final String INVALID = "INVALID"; private static final String TEST_IFNAME = "test_wlan0"; private static final int TEST_IFINDEX = 1001; + private static final String TEST_CLAT_IFNAME = "v4-" + TEST_IFNAME; + private static final int TEST_CLAT_IFINDEX = 1002; // See RFC 7042#section-2.1.2 for EUI-48 documentation values. private static final MacAddress TEST_MAC = MacAddress.fromString("00:00:5E:00:53:01"); private static final int TEST_TIMEOUT_MS = 30_000; @@ -301,6 +311,21 @@ (byte) 0 /* icmp_code */, option, null /* srcaddr */); } + private static RtNetlinkLinkMessage buildRtmLinkMessage(short type, int ifindex, + String ifaceName) { + final StructNlMsgHdr nlmsghdr = + makeNetlinkMessageHeader(type, (short) (NLM_F_REQUEST | NLM_F_ACK)); + final StructIfinfoMsg ifInfoMsg = + new StructIfinfoMsg( + (short) AF_UNSPEC, + ARPHRD_ETHER, + ifindex, + 0 /* flags */, + 0xffffffffL /* change */); + + return new RtNetlinkLinkMessage(nlmsghdr, 0 /* mtu */, ifInfoMsg, TEST_MAC, ifaceName); + } + private void onInterfaceAddressUpdated(final LinkAddress la, int flags) { final RtNetlinkAddressMessage msg = buildRtmAddressMessage(RTM_NEWADDR, la, TEST_IFINDEX, flags); @@ -322,6 +347,12 @@ mNetlinkMessageProcessor.processNetlinkMessage(msg, TEST_UNUSED_REAL_TIME /* whenMs */); } + private void onInterfaceAdded(int ifaceIndex, String ifaceName) { + final RtNetlinkLinkMessage msg = buildRtmLinkMessage(RTM_NEWLINK, ifaceIndex, ifaceName); + mNetlinkMessageProcessor.processNetlinkMessage(msg, TEST_UNUSED_REAL_TIME /* whenMs */); + } + + @Test public void testNullInterfaceNameMostDefinitelyThrows() throws Exception { setTestInterfaceParams(null); @@ -947,6 +978,80 @@ } @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void testVendorNdOffloadDisabledWhenApfV6Supported() throws Exception { + when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), anyBoolean())) + .thenReturn(mApfFilter); + when(mApfFilter.supportNdOffload()).thenReturn(true); + final IpClient ipc = makeIpClient(TEST_IFNAME); + ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIPv4() + .withoutIpReachabilityMonitor() + .withApfCapabilities(new ApfCapabilities(APF_VERSION_6, + 4096 /* maxProgramSize */, ARPHRD_ETHER)) + .build(); + ipc.startProvisioning(config); + final InOrder inOrder = inOrder(mCb); + inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setNeighborDiscoveryOffload(true); + inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setNeighborDiscoveryOffload(false); + + // update clat + onInterfaceAdded(TEST_CLAT_IFINDEX, TEST_CLAT_IFNAME); + verifyShutdown(ipc); + inOrder.verify(mCb, never()).setNeighborDiscoveryOffload(anyBoolean()); + clearInvocations(mApfFilter); + clearInvocations(mCb); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void testVendorNdOffloadEnabledWhenApfV6NotSupported() throws Exception { + when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), anyBoolean())) + .thenReturn(mApfFilter); + when(mApfFilter.supportNdOffload()).thenReturn(false); + final IpClient ipc = makeIpClient(TEST_IFNAME); + ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIPv4() + .withoutIpReachabilityMonitor() + .withApfCapabilities(new ApfCapabilities(APF_VERSION_6, + 4096 /* maxProgramSize */, ARPHRD_ETHER)) + .build(); + ipc.startProvisioning(config); + verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setNeighborDiscoveryOffload(true); + + // update clat + onInterfaceAdded(TEST_CLAT_IFINDEX, TEST_CLAT_IFNAME); + verifyShutdown(ipc); + verify(mCb, times(1)).setNeighborDiscoveryOffload(true); + clearInvocations(mApfFilter); + clearInvocations(mCb); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void testVendorNdOffloadDisabledWhenApfCapabilitiesUpdated() throws Exception { + when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), anyBoolean())) + .thenReturn(mApfFilter); + when(mApfFilter.supportNdOffload()).thenReturn(true); + final IpClient ipc = makeIpClient(TEST_IFNAME); + ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIPv4() + .withoutIpReachabilityMonitor() + .build(); + ipc.startProvisioning(config); + ipc.updateApfCapabilities( + new ApfCapabilities(APF_VERSION_6, 4096 /* maxProgramSize */, ARPHRD_ETHER)); + HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS); + final InOrder inOrder = inOrder(mCb); + inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setNeighborDiscoveryOffload(true); + inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setNeighborDiscoveryOffload(false); + verifyShutdown(ipc); + inOrder.verify(mCb, never()).setNeighborDiscoveryOffload(anyBoolean()); + clearInvocations(mApfFilter); + clearInvocations(mCb); + } + + @Test public void testLinkPropertiesUpdate_callSetLinkPropertiesOnApfFilter() throws Exception { when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), anyBoolean())) .thenReturn(mApfFilter);
diff --git a/tests/unit/src/android/net/util/RawSocketUtilsTest.kt b/tests/unit/src/android/net/util/RawSocketUtilsTest.kt new file mode 100644 index 0000000..45bee54 --- /dev/null +++ b/tests/unit/src/android/net/util/RawSocketUtilsTest.kt
@@ -0,0 +1,156 @@ +/* + * 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.util + +import android.content.Context +import android.net.TetheringManager +import android.system.Os +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.net.module.util.HexDump +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRunner +import java.io.FileDescriptor +import java.net.NetworkInterface +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import org.junit.After +import org.junit.Before +import org.junit.Rule +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.doAnswer +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.framework +import org.mockito.Mockito.`when` +import org.mockito.MockitoSession +import org.mockito.quality.Strictness + +@RunWith(DevSdkIgnoreRunner::class) +class RawSocketUtilsTest { + @get:Rule + val ignoreRule = DevSdkIgnoreRule() + companion object { + private const val TEST_IFINDEX = 123 + private const val TEST_IFACENAME = "wlan0" + private const val TEST_SRC_MAC = "FFFFFFFFFFFF" + private const val TEST_DST_MAC = "1234567890AB" + private const val TEST_INVALID_PACKET_IN_HEX = "DEADBEEF" + private const val TEST_PACKET_TYPE_IN_HEX = "88A4" + private const val TEST_VALID_PACKET_IN_HEX = + TEST_DST_MAC + TEST_SRC_MAC + TEST_PACKET_TYPE_IN_HEX + } + @Mock + private lateinit var mockContext: Context + @Mock + private lateinit var mockTetheringManager: TetheringManager + @Mock + private lateinit var mockNetworkInterface: NetworkInterface + + // For mocking static methods. + private lateinit var mockitoSession: MockitoSession + + @Before + fun setup() { + mockitoSession = ExtendedMockito.mockitoSession() + .mockStatic(Os::class.java) + .mockStatic(NetworkInterface::class.java) + .mockStatic(SocketUtils::class.java) + .initMocks(this) + .strictness(Strictness.LENIENT) + .startMocking() + doReturn(mockTetheringManager).`when`(mockContext) + .getSystemService(eq(TetheringManager::class.java)) + `when`(NetworkInterface.getByName(any())).thenReturn(mockNetworkInterface) + doReturn(TEST_IFINDEX).`when`(mockNetworkInterface).index + } + + @After + fun teardown() { + mockitoSession.finishMocking() + // Clear mocks to prevent from stubs holding instances and cause memory leaks. + framework().clearInlineMocks() + } + + @Test + fun sendRawPacketDownStream_invalidTetheredInterface() { + doAnswer { + val callback = it.arguments[1] as TetheringManager.TetheringEventCallback + callback.onTetheredInterfacesChanged(listOf("eth0")) + }.`when`(mockTetheringManager).registerTetheringEventCallback(any(), any()) + assertFailsWith<SecurityException> { + RawSocketUtils.sendRawPacketDownStream( + mockContext, + TEST_IFACENAME, + TEST_INVALID_PACKET_IN_HEX + ) + } + } + + @Test + fun sendRawPacketDownStream_invalidPacket() { + doAnswer { + val callback = it.arguments[1] as TetheringManager.TetheringEventCallback + callback.onTetheredInterfacesChanged(listOf(TEST_IFACENAME)) + }.`when`(mockTetheringManager).registerTetheringEventCallback(any(), any()) + + assertFailsWith<ArrayIndexOutOfBoundsException> { + RawSocketUtils.sendRawPacketDownStream( + mockContext, + TEST_IFACENAME, + TEST_INVALID_PACKET_IN_HEX + ) + } + } + + @Test + fun sendRawPacketDownStream_validPacket() { + doAnswer { + val callback = it.arguments[1] as TetheringManager.TetheringEventCallback + callback.onTetheredInterfacesChanged(listOf(TEST_IFACENAME)) + }.`when`(mockTetheringManager).registerTetheringEventCallback(any(), any()) + + RawSocketUtils.sendRawPacketDownStream( + mockContext, + TEST_IFACENAME, + TEST_VALID_PACKET_IN_HEX + ) + + // Verify interactions with mocked static methods. + val fileDescriptorCaptor = ArgumentCaptor.forClass(FileDescriptor::class.java) + val packetDataCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + val packetDataLengthCaptor = ArgumentCaptor.forClass(Int::class.java) + ExtendedMockito.verify { + Os.sendto( + fileDescriptorCaptor.capture(), + packetDataCaptor.capture(), + eq(0), + packetDataLengthCaptor.capture(), + eq(0), + any() + ) + } + assertEquals(TEST_VALID_PACKET_IN_HEX, HexDump.toHexString(packetDataCaptor.value)) + assertEquals(TEST_VALID_PACKET_IN_HEX.length / 2, packetDataLengthCaptor.value) + // TODO: Verify ifindex and packetType once the members of PacketSocketAddress + // can be accessed. + ExtendedMockito.verify { SocketUtils.closeSocket(eq(fileDescriptorCaptor.value)) } + } +}
diff --git a/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java index 8dc3d92..69464cf 100644 --- a/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java +++ b/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java
@@ -130,7 +130,7 @@ CounterName.CN_DROPPED_IPV6_NON_ICMP_MULTICAST); verifyCounterName(Counter.DROPPED_802_3_FRAME, CounterName.CN_DROPPED_802_3_FRAME); verifyCounterName(Counter.DROPPED_ETHERTYPE_NOT_ALLOWED, - CounterName.CN_DROPPED_ETHERTYPE_DENYLISTED); + CounterName.CN_DROPPED_ETHERTYPE_NOT_ALLOWED); verifyCounterName(Counter.DROPPED_ARP_REPLY_SPA_NO_HOST, CounterName.CN_DROPPED_ARP_REPLY_SPA_NO_HOST); verifyCounterName(Counter.DROPPED_IPV4_KEEPALIVE_ACK, @@ -143,5 +143,29 @@ verifyCounterName(Counter.DROPPED_IPV4_TCP_PORT7_UNICAST, CounterName.CN_UNKNOWN); verifyCounterName(Counter.DROPPED_ARP_NON_IPV4, CounterName.CN_DROPPED_ARP_NON_IPV4); verifyCounterName(Counter.DROPPED_ARP_UNKNOWN, CounterName.CN_DROPPED_ARP_UNKNOWN); + verifyCounterName(Counter.PASSED_ARP_BROADCAST_REPLY, + CounterName.CN_PASSED_ARP_BROADCAST_REPLY); + verifyCounterName(Counter.PASSED_ARP_REQUEST, CounterName.CN_PASSED_ARP_REQUEST); + verifyCounterName(Counter.PASSED_IPV4_FROM_DHCPV4_SERVER, + CounterName.CN_PASSED_IPV4_FROM_DHCPV4_SERVER); + verifyCounterName(Counter.PASSED_IPV6_NS_DAD, CounterName.CN_PASSED_IPV6_NS_DAD); + verifyCounterName(Counter.PASSED_IPV6_NS_NO_ADDRESS, + CounterName.CN_PASSED_IPV6_NS_NO_ADDRESS); + verifyCounterName(Counter.PASSED_IPV6_NS_NO_SLLA_OPTION, + CounterName.CN_PASSED_IPV6_NS_NO_SLLA_OPTION); + verifyCounterName(Counter.PASSED_IPV6_NS_TENTATIVE, + CounterName.CN_PASSED_IPV6_NS_TENTATIVE); + verifyCounterName(Counter.PASSED_MLD, CounterName.CN_PASSED_MLD); + verifyCounterName(Counter.DROPPED_IPV4_NON_DHCP4, CounterName.CN_DROPPED_IPV4_NON_DHCP4); + verifyCounterName(Counter.DROPPED_IPV6_NS_INVALID, CounterName.CN_DROPPED_IPV6_NS_INVALID); + verifyCounterName(Counter.DROPPED_IPV6_NS_OTHER_HOST, + CounterName.CN_DROPPED_IPV6_NS_OTHER_HOST); + verifyCounterName(Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD, + CounterName.CN_DROPPED_IPV6_NS_REPLIED_NON_DAD); + verifyCounterName(Counter.DROPPED_ARP_REQUEST_ANYHOST, + CounterName.CN_DROPPED_ARP_REQUEST_ANYHOST); + verifyCounterName(Counter.DROPPED_ARP_REQUEST_REPLIED, + CounterName.CN_DROPPED_ARP_REQUEST_REPLIED); + verifyCounterName(Counter.DROPPED_ARP_V6_ONLY, CounterName.CN_DROPPED_ARP_V6_ONLY); } }
diff --git a/tests/unit/src/com/android/server/connectivity/DdrTrackerTest.java b/tests/unit/src/com/android/server/connectivity/DdrTrackerTest.java new file mode 100644 index 0000000..dcc4291 --- /dev/null +++ b/tests/unit/src/com/android/server/connectivity/DdrTrackerTest.java
@@ -0,0 +1,100 @@ +/* + * 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.connectivity; + +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.annotation.NonNull; +import android.net.shared.PrivateDnsConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.MockitoAnnotations; + +import java.net.InetAddress; + +@RunWith(JUnit4.class) +public final class DdrTrackerTest { + private static final int OFF_MODE = PRIVATE_DNS_MODE_OFF; + private static final int OPPORTUNISTIC_MODE = PRIVATE_DNS_MODE_OPPORTUNISTIC; + private static final int STRICT_MODE = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + + private DdrTracker mDdrTracker; + + private static class PrivateDnsConfigBuilder { + private int mMode = OFF_MODE; + private String mHostname = null; + private final InetAddress[] mIps = null; + private final String mDohName = null; + private final InetAddress[] mDohIps = null; + private final String mDohPath = null; + private final int mDohPort = -1; + + PrivateDnsConfigBuilder setMode(int mode) { + mMode = mode; + return this; + } + PrivateDnsConfigBuilder setHostname(String value) { + mHostname = value; + return this; + } + PrivateDnsConfig build() { + return new PrivateDnsConfig(mMode, mHostname, mIps, mDohName, mDohIps, mDohPath, + mDohPort); + } + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mDdrTracker = new DdrTracker(); + } + + private void testNotifyPrivateDnsSettingsChangedHelper(int mode, @NonNull String dnsProvider) + throws Exception { + final PrivateDnsConfig cfg = + new PrivateDnsConfigBuilder().setMode(mode).setHostname(dnsProvider).build(); + + assertTrue(mDdrTracker.notifyPrivateDnsSettingsChanged(cfg)); + assertEquals(mode, mDdrTracker.getPrivateDnsMode()); + assertEquals(dnsProvider, mDdrTracker.getStrictModeHostname()); + assertFalse(mDdrTracker.notifyPrivateDnsSettingsChanged(cfg)); + } + + @Test + public void testNotifyPrivateDnsSettingsChanged() throws Exception { + // Tests the initial private DNS setting in DdrTracker. + assertEquals(OFF_MODE, mDdrTracker.getPrivateDnsMode()); + + assertEquals("", mDdrTracker.getStrictModeHostname()); + assertFalse(mDdrTracker.notifyPrivateDnsSettingsChanged(new PrivateDnsConfigBuilder() + .setMode(OFF_MODE).build())); + + testNotifyPrivateDnsSettingsChangedHelper(OPPORTUNISTIC_MODE, ""); + testNotifyPrivateDnsSettingsChangedHelper(STRICT_MODE, "example1.com"); + testNotifyPrivateDnsSettingsChangedHelper(STRICT_MODE, "example2.com"); + testNotifyPrivateDnsSettingsChangedHelper(OFF_MODE, ""); + } +}