Update APF multicast address when receiving IGMP report monitor callback
Test: atest NetworkStackTests
Change-Id: I56ef1a3275b8bed54439f1be50d48eb470351cec
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index d844dc1..456f43c 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -101,6 +101,7 @@
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
import static android.system.OsConstants.AF_PACKET;
+import static android.system.OsConstants.ETH_P_ALL;
import static android.system.OsConstants.ETH_P_ARP;
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.ETH_P_IPV6;
@@ -143,6 +144,7 @@
import android.net.TcpKeepalivePacketDataParcelable;
import android.net.apf.ApfCounterTracker.Counter;
import android.net.apf.BaseApfGenerator.IllegalInstructionException;
+import android.net.ip.IgmpReportMonitor;
import android.net.ip.IpClient.IpClientCallbacksWrapper;
import android.net.nsd.NsdManager;
import android.net.nsd.OffloadEngine;
@@ -217,6 +219,7 @@
public boolean shouldHandleArpOffload;
public boolean shouldHandleNdOffload;
public boolean shouldHandleMdnsOffload;
+ public boolean shouldHandleIgmpOffload;
}
@@ -284,11 +287,14 @@
private final boolean mShouldHandleArpOffload;
private final boolean mShouldHandleNdOffload;
private final boolean mShouldHandleMdnsOffload;
+ private final boolean mShouldHandleIgmpOffload;
private final NetworkQuirkMetrics mNetworkQuirkMetrics;
private final IpClientRaInfoMetrics mIpClientRaInfoMetrics;
private final ApfSessionInfoMetrics mApfSessionInfoMetrics;
private final NsdManager mNsdManager;
+ private final IgmpReportMonitor mIgmpReportMonitor;
+
@VisibleForTesting
final List<OffloadServiceInfo> mOffloadServiceInfos = new ArrayList<>();
private OffloadEngine mOffloadEngine;
@@ -442,6 +448,7 @@
mShouldHandleArpOffload = config.shouldHandleArpOffload;
mShouldHandleNdOffload = config.shouldHandleNdOffload;
mShouldHandleMdnsOffload = config.shouldHandleMdnsOffload;
+ mShouldHandleIgmpOffload = config.shouldHandleIgmpOffload;
mDependencies = dependencies;
mNetworkQuirkMetrics = networkQuirkMetrics;
mIpClientRaInfoMetrics = dependencies.getIpClientRaInfoMetrics();
@@ -476,6 +483,17 @@
Log.wtf(TAG, "Failed to start RaPacketReader");
}
+ mIgmpReportMonitor = new IgmpReportMonitor(
+ mHandler,
+ mInterfaceParams,
+ this::updateIPv4MulticastAddrs,
+ mDependencies.createEgressIgmpReportsReaderSocket(ifParams.index)
+ );
+
+ if (shouldEnableIgmpOffload()) {
+ mIgmpReportMonitor.start();
+ }
+
// Listen for doze-mode transition changes to enable/disable the IPv6 multicast filter.
mDependencies.addDeviceIdleReceiver(mDeviceIdleReceiver);
@@ -483,6 +501,9 @@
if (shouldEnableMdnsOffload()) {
registerOffloadEngine();
}
+
+ mIPv4MulticastAddresses =
+ new ArraySet<>(mDependencies.getIPv4MulticastAddresses(mInterfaceParams.name));
}
/**
@@ -514,6 +535,24 @@
}
/**
+ * Create a socket to read egress IGMPv2/v3 reports.
+ */
+ @Nullable
+ public FileDescriptor createEgressIgmpReportsReaderSocket(int ifIndex) {
+ FileDescriptor socket;
+ try {
+ socket = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0);
+ NetworkStackUtils.attachEgressIgmpReportFilter(socket);
+ Os.bind(socket, makePacketSocketAddress(ETH_P_ALL, ifIndex));
+ } catch (SocketException | ErrnoException e) {
+ Log.wtf(TAG, "Error starting filter", e);
+ return null;
+ }
+
+ return socket;
+ }
+
+ /**
* Get elapsedRealtime.
*/
public long elapsedRealtime() {
@@ -2604,6 +2643,10 @@
if (shouldEnableMdnsOffload()) {
unregisterOffloadEngine();
}
+
+ if (shouldEnableIgmpOffload()) {
+ mIgmpReportMonitor.stop();
+ }
}
public void setMulticastFilter(boolean isEnabled) {
@@ -2710,6 +2753,10 @@
return shouldUseApfV6Generator() && mShouldHandleMdnsOffload;
}
+ private boolean shouldEnableIgmpOffload() {
+ return shouldUseApfV6Generator() && mShouldHandleIgmpOffload;
+ }
+
private boolean shouldUseApfV6Generator() {
return SdkLevel.isAtLeastV() && ApfV6Generator.supportsVersion(mApfVersionSupported);
}
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index a99a198..d23d30c 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -808,6 +808,7 @@
private final boolean mApfShouldHandleArpOffload;
private final boolean mApfShouldHandleNdOffload;
private final boolean mApfShouldHandleMdnsOffload;
+ private final boolean mApfShouldHandleIgmpOffload;
private final boolean mIgnoreNudFailureEnabled;
private final boolean mDhcp6PdPreferredFlagEnabled;
@@ -1071,6 +1072,8 @@
mContext, APF_HANDLE_ND_OFFLOAD);
// TODO: turn on APF mDNS offload.
mApfShouldHandleMdnsOffload = false;
+ // TODO: turn on APF IGMP offload.
+ mApfShouldHandleIgmpOffload = false;
mPopulateLinkAddressLifetime = mDependencies.isFeatureEnabled(context,
IPCLIENT_POPULATE_LINK_ADDRESS_LIFETIME_VERSION);
mIgnoreNudFailureEnabled = mDependencies.isFeatureEnabled(mContext,
@@ -2764,6 +2767,7 @@
apfConfig.shouldHandleArpOffload = mApfShouldHandleArpOffload;
apfConfig.shouldHandleNdOffload = mApfShouldHandleNdOffload;
apfConfig.shouldHandleMdnsOffload = mApfShouldHandleMdnsOffload;
+ apfConfig.shouldHandleIgmpOffload = mApfShouldHandleIgmpOffload;
apfConfig.minMetricsSessionDurationMs = mApfCounterPollingIntervalMs;
apfConfig.hasClatInterface = mHasSeenClatInterface;
return mDependencies.maybeCreateApfFilter(getHandler(), mContext, apfConfig,
diff --git a/tests/unit/src/android/net/apf/ApfFilterTest.kt b/tests/unit/src/android/net/apf/ApfFilterTest.kt
index 01c1260..3fa8e8d 100644
--- a/tests/unit/src/android/net/apf/ApfFilterTest.kt
+++ b/tests/unit/src/android/net/apf/ApfFilterTest.kt
@@ -133,6 +133,7 @@
class ApfFilterTest {
companion object {
private const val THREAD_QUIT_MAX_RETRY_COUNT = 3
+ private const val NO_CALLBACK_TIMEOUT_MS: Long = 500
private const val TAG = "ApfFilterTest"
}
@@ -206,6 +207,7 @@
}
private val handler by lazy { Handler(handlerThread.looper) }
private var writerSocket = FileDescriptor()
+ private var igmpWriteSocket = FileDescriptor()
@Before
fun setUp() {
@@ -227,6 +229,9 @@
val readSocket = FileDescriptor()
Os.socketpair(AF_UNIX, SOCK_STREAM, 0, writerSocket, readSocket)
doReturn(readSocket).`when`(dependencies).createPacketReaderSocket(anyInt())
+ val igmpReadSocket = FileDescriptor()
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, igmpWriteSocket, igmpReadSocket)
+ doReturn(igmpReadSocket).`when`(dependencies).createEgressIgmpReportsReaderSocket(anyInt())
doReturn(nsdManager).`when`(context).getSystemService(NsdManager::class.java)
}
@@ -253,6 +258,7 @@
@After
fun tearDown() {
IoUtils.closeQuietly(writerSocket)
+ IoUtils.closeQuietly(igmpWriteSocket)
shutdownApfFilters()
handler.waitForIdle(TIMEOUT_MS)
Mockito.framework().clearInlineMocks()
@@ -2124,6 +2130,35 @@
verify(ipClientCallback, never()).installPacketFilter(any())
}
+ // The APFv6 code path is only turned on in V+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testApfProgramUpdateWithMulticastAddressChange() {
+ val mcastAddrs = mutableListOf(
+ InetAddress.getByName("224.0.0.1") as Inet4Address
+ )
+ doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any())
+ val apfConfig = getDefaultConfig()
+ apfConfig.shouldHandleIgmpOffload = true
+ val apfFilter = getApfFilter(apfConfig)
+ consumeInstalledProgram(ipClientCallback, installCnt = 2)
+ val addr = InetAddress.getByName("239.0.0.1") as Inet4Address
+ mcastAddrs.add(addr)
+ doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any())
+ val testPacket = HexDump.hexStringToByteArray("000000")
+ Os.write(igmpWriteSocket, testPacket, 0, testPacket.size)
+ consumeInstalledProgram(ipClientCallback, installCnt = 1)
+
+ Os.write(igmpWriteSocket, testPacket, 0, testPacket.size)
+ Thread.sleep(NO_CALLBACK_TIMEOUT_MS)
+ verify(ipClientCallback, never()).installPacketFilter(any())
+
+ mcastAddrs.remove(addr)
+ doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any())
+ Os.write(igmpWriteSocket, testPacket, 0, testPacket.size)
+ consumeInstalledProgram(ipClientCallback, installCnt = 1)
+ }
+
@Test
fun testApfFilterInitializationCleanUpTheApfMemoryRegion() {
val apfFilter = getApfFilter()