Snap for 12770256 from eeb68e46d76e8a0c789234ac72ac47fc28b936ef to 25Q1-release Change-Id: I3ec280e2f6b3cf216edbbeae2a8d416ea170a4e5
diff --git a/src/android/net/apf/AndroidPacketFilter.java b/src/android/net/apf/AndroidPacketFilter.java index c88587b..c9f8aba 100644 --- a/src/android/net/apf/AndroidPacketFilter.java +++ b/src/android/net/apf/AndroidPacketFilter.java
@@ -120,4 +120,9 @@ default boolean shouldEnableMdnsOffload() { return false; } + + /** + * Update the multicast addresses that will be used by APF. + */ + default void updateIPv4MulticastAddrs() {} }
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index 08a370d..52b80ba 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java
@@ -350,6 +350,10 @@ // Our tentative IPv6 addresses private Set<Inet6Address> mIPv6TentativeAddresses = new ArraySet<>(); + // Our joined IPv4 multicast addresses + @VisibleForTesting + public Set<Inet4Address> mIPv4MulticastAddresses = new ArraySet<>(); + // Whether CLAT is enabled. private boolean mHasClat; @@ -603,6 +607,14 @@ public int getNdTrafficClass(@NonNull String ifname) { return ProcfsParsingUtils.getNdTrafficClass(ifname); } + + /** + * Loads the existing IPv4 multicast addresses from the file + * `/proc/net/igmp`. + */ + public List<Inet4Address> getIPv4MulticastAddresses(@NonNull String ifname) { + return ProcfsParsingUtils.getIPv4MulticastAddresses(ifname); + } } @Override @@ -2681,6 +2693,17 @@ } @Override + public void updateIPv4MulticastAddrs() { + final Set<Inet4Address> mcastAddrs = + new ArraySet<>(mDependencies.getIPv4MulticastAddresses(mInterfaceParams.name)); + + if (!mIPv4MulticastAddresses.equals(mcastAddrs)) { + mIPv4MulticastAddresses = mcastAddrs; + installNewProgram(); + } + } + + @Override public boolean supportNdOffload() { return shouldUseApfV6Generator() && mShouldHandleNdOffload; }
diff --git a/src/android/net/apf/ProcfsParsingUtils.java b/src/android/net/apf/ProcfsParsingUtils.java index 4bac0f8..0d931a7 100644 --- a/src/android/net/apf/ProcfsParsingUtils.java +++ b/src/android/net/apf/ProcfsParsingUtils.java
@@ -15,18 +15,23 @@ */ package android.net.apf; +import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ALL_HOST_MULTICAST; + import android.annotation.NonNull; import android.net.MacAddress; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.HexDump; +import com.android.net.module.util.HexDump; import java.io.BufferedReader; import java.io.IOException; +import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; @@ -39,6 +44,7 @@ private static final String IPV6_CONF_PATH = "/proc/sys/net/ipv6/conf/"; private static final String IPV6_ANYCAST_PATH = "/proc/net/anycast6"; private static final String ETHER_MCAST_PATH = "/proc/net/dev_mcast"; + private static final String IPV4_MCAST_PATH = "/proc/net/igmp"; private static final String IPV6_MCAST_PATH = "/proc/net/igmp6"; private ProcfsParsingUtils() { @@ -172,6 +178,75 @@ return addresses; } + + /** + * Parses IPv4 multicast addresses associated with a specific interface from a list of strings. + * + * @param lines A list of strings, each containing interface and IPv4 address information. + * @param ifname The name of the network interface for which to extract multicast addresses. + * @param endian The byte order of the address, almost always use native order. + * @return A list of Inet4Address objects representing the parsed IPv4 multicast addresses. + * If an error occurs during parsing, + * a list contains IPv4 all host (224.0.0.1) is returned. + */ + @VisibleForTesting + public static List<Inet4Address> parseIPv4MulticastAddresses( + @NonNull List<String> lines, @NonNull String ifname, @NonNull ByteOrder endian) { + final List<Inet4Address> ipAddresses = new ArrayList<>(); + + try { + String name = ""; + // parse output similar to `ip maddr` command (iproute2/ip/ipmaddr.c#read_igmp()) + for (String line : lines) { + final String[] parts = line.trim().split("\\s+"); + if (!line.startsWith("\t")) { + name = parts[1]; + if (name.endsWith(":")) { + name = name.substring(0, name.length() - 1); + } + continue; + } + + if (!name.equals(ifname)) { + continue; + } + + final String hexIp = parts[0]; + final byte[] ipArray = HexDump.hexStringToByteArray(hexIp); + final byte[] convertArray = + (endian == ByteOrder.LITTLE_ENDIAN) + ? convertIPv4BytesToBigEndian(ipArray) : ipArray; + final Inet4Address ipv4Address = + (Inet4Address) InetAddress.getByAddress(convertArray); + + ipAddresses.add(ipv4Address); + } + } catch (UnknownHostException | IllegalArgumentException e) { + Log.wtf(TAG, "failed to convert to Inet4Address.", e); + // always return IPv4 all host address (224.0.0.1) if any error during parsing. + // this aligns with kernel behavior, it will join 224.0.0.1 when the interface is up. + ipAddresses.clear(); + ipAddresses.add(IPV4_ADDR_ALL_HOST_MULTICAST); + } + + return ipAddresses; + } + + /** + * Converts an IPv4 address from little-endian byte order to big-endian byte order. + * + * @param bytes The IPv4 address in little-endian byte order. + * @return The IPv4 address in big-endian byte order. + */ + private static byte[] convertIPv4BytesToBigEndian(byte[] bytes) { + final ByteBuffer buffer = ByteBuffer.wrap(bytes); + buffer.order(ByteOrder.LITTLE_ENDIAN); + final ByteBuffer bigEndianBuffer = ByteBuffer.allocate(4); + bigEndianBuffer.order(ByteOrder.BIG_ENDIAN); + bigEndianBuffer.putInt(buffer.getInt()); + return bigEndianBuffer.array(); + } + /** * Returns the traffic class for the specified interface. * The function loads the existing traffic class from the file @@ -228,4 +303,19 @@ final List<String> lines = readFile(IPV6_MCAST_PATH); return parseIPv6MulticastAddresses(lines, ifname); } + + /** + * The function loads the existing IPv4 multicast addresses from the file `/proc/net/igmp6`. + * If the file does not exist or the interface is not found, the function returns empty list. + * + * @param ifname The name of the network interface to query. + * @return A list of Inet4Address objects representing the IPv4 multicast addresses + * found for the interface. + * If the file cannot be read or there are no addresses, an empty list is returned. + */ + public static List<Inet4Address> getIPv4MulticastAddresses(@NonNull String ifname) { + final List<String> lines = readFile(IPV4_MCAST_PATH); + // follow the same pattern as NetlinkMonitor#handlePacket() for device's endian order + return parseIPv4MulticastAddresses(lines, ifname, ByteOrder.nativeOrder()); + } }
diff --git a/tests/unit/src/android/net/apf/ApfFilterTest.kt b/tests/unit/src/android/net/apf/ApfFilterTest.kt index 1d821ef..0645cdd 100644 --- a/tests/unit/src/android/net/apf/ApfFilterTest.kt +++ b/tests/unit/src/android/net/apf/ApfFilterTest.kt
@@ -98,6 +98,7 @@ import com.android.testutils.quitResources import com.android.testutils.waitForIdle import java.io.FileDescriptor +import java.net.Inet4Address import java.net.Inet6Address import java.net.InetAddress import kotlin.test.assertContentEquals @@ -2140,4 +2141,29 @@ val program = consumeInstalledProgram(ipClientCallback, installCnt = 1) assertContentEquals(ByteArray(4096) { 0 }, program) } + + @Test + fun testApfIPv4MulticastAddrsUpdate() { + val apfFilter = getApfFilter() + // mock IPv4 multicast address from /proc/net/igmp + val mcastAddrs = mutableListOf( + InetAddress.getByName("224.0.0.1") as Inet4Address + ) + consumeInstalledProgram(ipClientCallback, installCnt = 2) + + doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any()) + apfFilter.updateIPv4MulticastAddrs() + consumeInstalledProgram(ipClientCallback, installCnt = 1) + assertEquals(mcastAddrs.toSet(), apfFilter.mIPv4MulticastAddresses) + + val addr = InetAddress.getByName("239.0.0.1") as Inet4Address + mcastAddrs.add(addr) + doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any()) + apfFilter.updateIPv4MulticastAddrs() + consumeInstalledProgram(ipClientCallback, installCnt = 1) + assertEquals(mcastAddrs.toSet(), apfFilter.mIPv4MulticastAddresses) + + apfFilter.updateIPv4MulticastAddrs() + verify(ipClientCallback, never()).installPacketFilter(any()) + } }
diff --git a/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt b/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt index 7f8cacb..f23f7f6 100644 --- a/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt +++ b/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt
@@ -18,9 +18,11 @@ import android.net.MacAddress import android.net.apf.ProcfsParsingUtils import androidx.test.filters.SmallTest -import com.android.internal.util.HexDump +import com.android.net.module.util.HexDump +import java.net.Inet4Address import java.net.Inet6Address import java.net.InetAddress +import java.nio.ByteOrder import kotlin.test.assertEquals import org.junit.Test @@ -135,4 +137,149 @@ ProcfsParsingUtils.parseIPv6MulticastAddresses(inputString, "wlan0") ) } + + @Test + fun testParseIpv4MulticastAddressLittleEndian() { + val order = ByteOrder.LITTLE_ENDIAN + + // the format refer to net/ipv4/igmp.c#igmp_mc_seq_show + val inputString = listOf( + "Idx\tDevice : Count Querier\tGroup Users Timer\tReporter", + "1\tlo : 1 V3", + "\t\t\t\t010000E0 1 0:00000000\t\t0", + "2\tdummy0 : 1 V3", + "\t\t\t\t010000E0 1 0:00000000\t\t0", + "47\twlan0 : 1 V3", + "\t\t\t\t020000EF 1 0:00000000\t\t0", + "\t\t\t\t010000EF 1 0:00000000\t\t0", + "\t\t\t\t010000E0 1 0:00000000\t\t0", + "51\tv4-wlan0 : 1 V3", + "\t\t\t\t010000E0 1 0:00000000\t\t0" + ) + + val expectedResult = listOf( + InetAddress.getByAddress( + HexDump.hexStringToByteArray("EF000002") + ) as Inet4Address, + InetAddress.getByAddress( + HexDump.hexStringToByteArray("EF000001") + ) as Inet4Address, + InetAddress.getByAddress( + HexDump.hexStringToByteArray("E0000001") + ) as Inet4Address, + ) + + assertEquals( + expectedResult, + ProcfsParsingUtils.parseIPv4MulticastAddresses( + inputString, "wlan0", order) + ) + + assertEquals( + emptyList<Inet4Address>(), + ProcfsParsingUtils.parseIPv4MulticastAddresses( + inputString, "eth0", order) + ) + + assertEquals( + emptyList<Inet4Address>(), + ProcfsParsingUtils.parseIPv4MulticastAddresses( + emptyList<String>(), "eth0", order) + ) + } + + @Test + fun testParseIpv4MulticastAddressBigEndian() { + val order = ByteOrder.BIG_ENDIAN + + // the format refer to net/ipv4/igmp.c#igmp_mc_seq_show + val inputString = listOf( + "Idx\tDevice : Count Querier\tGroup Users Timer\tReporter", + "1\tlo : 1 V3", + "\t\t\t\tE0000001 1 0:00000000\t\t0", + "2\tdummy0 : 1 V3", + "\t\t\t\tE0000001 1 0:00000000\t\t0", + "47\twlan0 : 1 V3", + "\t\t\t\tEF000002 1 0:00000000\t\t0", + "\t\t\t\tEF000001 1 0:00000000\t\t0", + "\t\t\t\tE0000001 1 0:00000000\t\t0", + "51\tv4-wlan0 : 1 V3", + "\t\t\t\tE0000001 1 0:00000000\t\t0" + ) + + val expectedResult = listOf( + InetAddress.getByAddress( + HexDump.hexStringToByteArray("EF000002") + ) as Inet4Address, + InetAddress.getByAddress( + HexDump.hexStringToByteArray("EF000001") + ) as Inet4Address, + InetAddress.getByAddress( + HexDump.hexStringToByteArray("E0000001") + ) as Inet4Address, + ) + + assertEquals( + expectedResult, + ProcfsParsingUtils.parseIPv4MulticastAddresses( + inputString, "wlan0", order) + ) + + assertEquals( + emptyList<Inet4Address>(), + ProcfsParsingUtils.parseIPv4MulticastAddresses( + inputString, "eth0", order) + ) + + assertEquals( + emptyList<Inet4Address>(), + ProcfsParsingUtils.parseIPv4MulticastAddresses( + emptyList<String>(), "eth0", order) + ) + } + + @Test + fun testParseIpv4MulticastAddressError() { + val order = ByteOrder.LITTLE_ENDIAN + + // the format refer to net/ipv4/igmp.c#igmp_mc_seq_show + // wlan0 addresses contain invalid char 'X' + val inputString = listOf( + "Idx\tDevice : Count Querier\tGroup Users Timer\tReporter", + "1\tlo : 1 V3", + "\t\t\t\t010000E0 1 0:00000000\t\t0", + "2\tdummy0 : 1 V3", + "\t\t\t\t010000E0 1 0:00000000\t\t0", + "47\twlan0 : 1 V3", + "\t\t\t\t02XXXXEF 1 0:00000000\t\t0", + "\t\t\t\t01XXXXEF 1 0:00000000\t\t0", + "\t\t\t\t01XXXXE0 1 0:00000000\t\t0", + "51\tv4-wlan0 : 1 V3", + "\t\t\t\t010000E0 1 0:00000000\t\t0" + ) + + val expectedResult = listOf( + InetAddress.getByAddress( + HexDump.hexStringToByteArray("E0000001") + ) as Inet4Address + ) + + assertEquals( + expectedResult, + ProcfsParsingUtils.parseIPv4MulticastAddresses( + inputString, "wlan0", order) + ) + + assertEquals( + emptyList<Inet4Address>(), + ProcfsParsingUtils.parseIPv4MulticastAddresses( + inputString, "eth0", order) + ) + + assertEquals( + emptyList<Inet4Address>(), + ProcfsParsingUtils.parseIPv4MulticastAddresses( + emptyList<String>(), "eth0", order) + ) + } }