Snap for 9560500 from 55f4a26c7a4f2ba2e38c39f62bdee4c834817218 to sdk-release

Change-Id: I420e81dd2c6c505fb2c77c7093a022528f992ac4
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 70fa673..e4c8015 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -69,6 +69,7 @@
 import com.android.net.module.util.SocketUtils;
 import com.android.networkstack.util.NetworkStackUtils;
 
+import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.Inet4Address;
@@ -80,8 +81,10 @@
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * For networks that support packet filtering via APF programs, {@code ApfFilter}
@@ -144,6 +147,7 @@
         PASSED_ARP_UNKNOWN,
         PASSED_ARP_UNICAST_REPLY,
         PASSED_NON_IP_UNICAST,
+        PASSED_MDNS,
         DROPPED_ETH_BROADCAST,
         DROPPED_RA,
         DROPPED_GARP_REPLY,
@@ -162,7 +166,8 @@
         DROPPED_ARP_REPLY_SPA_NO_HOST,
         DROPPED_IPV4_KEEPALIVE_ACK,
         DROPPED_IPV6_KEEPALIVE_ACK,
-        DROPPED_IPV4_NATT_KEEPALIVE;
+        DROPPED_IPV4_NATT_KEEPALIVE,
+        DROPPED_MDNS;
 
         // Returns the negative byte offset from the end of the APF data segment for
         // a given counter.
@@ -343,6 +348,22 @@
     // TODO: Select a proper max length
     private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
 
+    private static final byte[] ETH_MULTICAST_MDNS_V4_MAC_ADDRESS =
+            {(byte) 0x01, (byte) 0x00, (byte) 0x5e, (byte) 0x00, (byte) 0x00, (byte) 0xfb};
+    private static final byte[] ETH_MULTICAST_MDNS_V6_MAC_ADDRESS =
+            {(byte) 0x33, (byte) 0x33, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xfb};
+    private static final int MDNS_PORT = 5353;
+    private static final int DNS_HEADER_LEN = 12;
+    private static final int DNS_QDCOUNT_OFFSET = 4;
+    // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT, or the
+    // IPv6 header length.
+    private static final int MDNS_QDCOUNT_OFFSET =
+            ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_QDCOUNT_OFFSET;
+    private static final int MDNS_QNAME_OFFSET =
+            ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_HEADER_LEN;
+
+
+
     private final ApfCapabilities mApfCapabilities;
     private final IpClientCallbacksWrapper mIpClientCallback;
     private final InterfaceParams mInterfaceParams;
@@ -902,6 +923,18 @@
             return currentLifetime() <= 0;
         }
 
+        // Filter for a fraction of the lifetime and adjust for the age of the RA.
+        @GuardedBy("ApfFilter.this")
+        int filterLifetime() {
+            return (int) (mMinLifetime / FRACTION_OF_LIFETIME_TO_FILTER)
+                    - (int) (mProgramBaseTime - mLastSeen);
+        }
+
+        @GuardedBy("ApfFilter.this")
+        boolean shouldFilter() {
+            return filterLifetime() > 0;
+        }
+
         // Append a filter for this RA to {@code gen}. Jump to DROP_LABEL if it should be dropped.
         // Jump to the next filter if packet doesn't match this RA.
         // Return Long.MAX_VALUE if we don't install any filter program for this RA. As the return
@@ -910,18 +943,13 @@
         // filter gets generated makes sure the program lifetime stays unaffected.
         @GuardedBy("ApfFilter.this")
         long generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
-            // Filter for a fraction of the lifetime and adjust for the age of the RA.
-            int filterLifetime = (int) (mMinLifetime / FRACTION_OF_LIFETIME_TO_FILTER)
-                    - (int) (mProgramBaseTime - mLastSeen);
-            if (filterLifetime <= 0) return Long.MAX_VALUE;
-
             String nextFilterLabel = "Ra" + getUniqueNumberLocked();
             // Skip if packet is not the right size
             gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
             gen.addJumpIfR0NotEquals(mPacket.capacity(), nextFilterLabel);
             // Skip filter if expired
             gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
-            gen.addJumpIfR0GreaterThan(filterLifetime, nextFilterLabel);
+            gen.addJumpIfR0GreaterThan(filterLifetime(), nextFilterLabel);
             for (PacketSection section : mPacketSections) {
                 // Generate code to match the packet bytes.
                 if (section.type == PacketSection.Type.MATCH) {
@@ -942,13 +970,13 @@
                             throw new IllegalStateException(
                                     "bogus lifetime size " + section.length);
                     }
-                    gen.addJumpIfR0LessThan(filterLifetime, nextFilterLabel);
+                    gen.addJumpIfR0LessThan(filterLifetime(), nextFilterLabel);
                 }
             }
             maybeSetupCounter(gen, Counter.DROPPED_RA);
             gen.addJump(mCountAndDropLabel);
             gen.defineLabel(nextFilterLabel);
-            return filterLifetime;
+            return filterLifetime();
         }
     }
 
@@ -1175,6 +1203,8 @@
     private ArrayList<Ra> mRas = new ArrayList<>();
     @GuardedBy("this")
     private SparseArray<KeepalivePacket> mKeepalivePackets = new SparseArray<>();
+    @GuardedBy("this")
+    private final List<String[]> mMdnsAllowList = new ArrayList<>();
 
     // There is always some marginal benefit to updating the installed APF program when an RA is
     // seen because we can extend the program's lifetime slightly, but there is some cost to
@@ -1493,6 +1523,124 @@
         gen.defineLabel(skipUnsolicitedMulticastNALabel);
     }
 
+    /** Encodes qname in TLV pattern. */
+    @VisibleForTesting
+    public static byte[] encodeQname(String[] labels) {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        for (String label : labels) {
+            byte[] labelBytes = label.getBytes(StandardCharsets.UTF_8);
+            out.write(labelBytes.length);
+            out.write(labelBytes, 0, labelBytes.length);
+        }
+        out.write(0);
+        return out.toByteArray();
+    }
+
+    /**
+     * Generate filter code to process mDNS packets. Execution of this code ends in * DROP_LABEL
+     * or PASS_LABEL if the packet is mDNS packets. Otherwise, skip this check.
+     */
+    @GuardedBy("this")
+    private void generateMdnsFilterLocked(ApfGenerator gen)
+            throws IllegalInstructionException {
+        final String skipMdnsv4Filter = "skip_mdns_v4_filter";
+        final String skipMdnsFilter = "skip_mdns_filter";
+        final String checkMdnsUdpPort = "check_mdns_udp_port";
+        final String mDnsAcceptPacket = "mdns_accept_packet";
+        final String mDnsDropPacket = "mdns_drop_packet";
+
+        // Only turn on the filter if multicast filter is on and the qname allowlist is non-empty.
+        if (!mMulticastFilter || mMdnsAllowList.isEmpty()) {
+            return;
+        }
+
+        // Here's a basic summary of what the mDNS filter program does:
+        //
+        // if it is a multicast mDNS packet
+        //    if QDCOUNT > 1 and the first QNAME is in the allowlist
+        //       pass
+        //    else:
+        //       drop
+        //
+        // A packet is considered as a multicast mDNS packet if it matches all the following
+        // conditions
+        //   1. its destination MAC address matches 01:00:5E:00:00:FB or 33:33:00:00:00:FB, for
+        //   v4 and v6 respectively.
+        //   2. it is an IPv4/IPv6 packet
+        //   3. it is a UDP packet with port 5353
+
+        // Check it's L2 mDNS multicast address.
+        gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
+        gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS,
+                skipMdnsv4Filter);
+
+        // Checks it's IPv4.
+        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+        gen.addJumpIfR0NotEquals(ETH_P_IP, skipMdnsFilter);
+
+        // Checks it's UDP.
+        gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET);
+        gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter);
+        // Set R1 to IPv4 header.
+        gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+        gen.addJump(checkMdnsUdpPort);
+
+        gen.defineLabel(skipMdnsv4Filter);
+
+        // Checks it's L2 mDNS multicast address.
+        // Relies on R0 containing the ethernet destination mac address offset.
+        gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS,
+                skipMdnsFilter);
+
+        // Checks it's IPv6.
+        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+        gen.addJumpIfR0NotEquals(ETH_P_IPV6, skipMdnsFilter);
+
+        // Checks it's UDP.
+        gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET);
+        gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter);
+
+        // Set R1 to IPv6 header.
+        gen.addLoadImmediate(Register.R1, IPV6_HEADER_LEN);
+
+        // Checks it's mDNS UDP port
+        gen.defineLabel(checkMdnsUdpPort);
+        gen.addLoad16Indexed(Register.R0, UDP_DESTINATION_PORT_OFFSET);
+        gen.addJumpIfR0NotEquals(MDNS_PORT, skipMdnsFilter);
+
+        // Only do the QNAME check if the QDCOUNT is more than 0.
+        // If there is more than one query (QDCOUNT > 1), we only matches the first QNAME.
+        gen.addLoad16Indexed(Register.R0, MDNS_QDCOUNT_OFFSET);
+        gen.addJumpIfR0Equals(0, mDnsDropPacket);
+
+        // Load offset for the first QNAME.
+        gen.addLoadImmediate(Register.R0, MDNS_QNAME_OFFSET);
+        gen.addAddR1();
+
+        // Check first QNAME against allowlist
+        for (int i = 0; i < mMdnsAllowList.size(); ++i) {
+            final String mDnsNextAllowedQnameCheck = "mdns_next_allowed_qname_check" + i;
+            final byte[] encodedQname = encodeQname(mMdnsAllowList.get(i));
+            gen.addJumpIfBytesNotEqual(Register.R0, encodedQname, mDnsNextAllowedQnameCheck);
+            // QNAME matched
+            gen.addJump(mDnsAcceptPacket);
+            // QNAME not matched
+            gen.defineLabel(mDnsNextAllowedQnameCheck);
+        }
+        // If QNAME doesn't match any entries in allowlist, drop the packet.
+        gen.defineLabel(mDnsDropPacket);
+        maybeSetupCounter(gen, Counter.DROPPED_MDNS);
+        gen.addJump(mCountAndDropLabel);
+
+        gen.defineLabel(mDnsAcceptPacket);
+        maybeSetupCounter(gen, Counter.PASSED_MDNS);
+        gen.addJump(mCountAndPassLabel);
+
+
+        gen.defineLabel(skipMdnsFilter);
+    }
+
+
     private void generateV6KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException {
         generateKeepaliveFilters(gen, TcpKeepaliveAckV6.class, IPPROTO_TCP, IPV6_NEXT_HEADER_OFFSET,
                 "skip_v6_keepalive_filter");
@@ -1567,19 +1715,20 @@
         generateArpFilterLocked(gen);
         gen.defineLabel(skipArpFiltersLabel);
 
+        // Add mDNS filter:
+        generateMdnsFilterLocked(gen);
+        gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
+
         // Add IPv4 filters:
         String skipIPv4FiltersLabel = "skipIPv4Filters";
-        // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did not
-        // execute the ARP filter, since that filter does not fall through, but either drops or
-        // passes.
         gen.addJumpIfR0NotEquals(ETH_P_IP, skipIPv4FiltersLabel);
         generateIPv4FilterLocked(gen);
         gen.defineLabel(skipIPv4FiltersLabel);
 
         // Check for IPv6:
-        // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did not
-        // execute the ARP or IPv4 filters, since those filters do not fall through, but either
-        // drop or pass.
+        // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did
+        // not execute the IPv4 filter, since this filter do not fall through, but either drop or
+        // pass.
         String ipv6FilterLabel = "IPv6Filters";
         gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel);
 
@@ -1661,6 +1810,7 @@
             }
 
             for (Ra ra : mRas) {
+                if (!ra.shouldFilter()) continue;
                 ra.generateFilterLocked(gen);
                 // Stop if we get too big.
                 if (gen.programLengthOverEstimate() > maximumApfProgramSize) {
@@ -1755,7 +1905,6 @@
                 if (VDBG) log("matched RA " + ra);
                 // Update lifetimes.
                 ra.mLastSeen = currentTimeSeconds();
-                ra.mMinLifetime = ra.minLifetime();
                 ra.seenCount++;
 
                 // Keep mRas in LRU order so as to prioritize generating filters for recently seen
@@ -1843,6 +1992,22 @@
         installNewProgramLocked();
     }
 
+    /** Adds qname to the mDNS allowlist */
+    public synchronized void addToMdnsAllowList(String[] labels) {
+        mMdnsAllowList.add(labels);
+        if (mMulticastFilter) {
+            installNewProgramLocked();
+        }
+    }
+
+    /** Removes qname from the mDNS allowlist */
+    public synchronized void removeFromAllowList(String[] labels) {
+        mMdnsAllowList.removeIf(e -> Arrays.equals(labels, e));
+        if (mMulticastFilter) {
+            installNewProgramLocked();
+        }
+    }
+
     @VisibleForTesting
     public synchronized void setDozeMode(boolean isEnabled) {
         if (mInDozeMode == isEnabled) return;
diff --git a/tests/integration/AndroidTest_Coverage.xml b/tests/integration/AndroidTest_Coverage.xml
index 1c63a93..5e5fbfb 100644
--- a/tests/integration/AndroidTest_Coverage.xml
+++ b/tests/integration/AndroidTest_Coverage.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Runs coverage tests for NetworkStack">
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="NetworkStackCoverageTests.apk" />
     </target_preparer>
 
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index 0846127..ea4dbc8 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -42,6 +42,7 @@
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.MacAddress;
 import android.net.NattKeepalivePacketDataParcelable;
 import android.net.TcpKeepalivePacketDataParcelable;
 import android.net.apf.ApfFilter.ApfConfiguration;
@@ -64,9 +65,11 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.HexDump;
+import com.android.net.module.util.DnsPacket;
 import com.android.net.module.util.Inet4AddressUtils;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.PacketBuilder;
 import com.android.net.module.util.SharedLog;
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
 import com.android.server.networkstack.tests.R;
@@ -74,6 +77,7 @@
 import libcore.io.IoUtils;
 import libcore.io.Streams;
 
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -88,6 +92,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
@@ -1020,6 +1025,10 @@
     private static final int ETH_ETHERTYPE_OFFSET         = 12;
     private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
             {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
+    private static final byte[] ETH_MULTICAST_MDNS_v4_MAC_ADDRESS =
+            {(byte) 0x01, (byte) 0x00, (byte) 0x5e, (byte) 0x00, (byte) 0x00, (byte) 0xfb};
+    private static final byte[] ETH_MULTICAST_MDNS_V6_MAC_ADDRESS =
+            {(byte) 0x33, (byte) 0x33, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xfb};
 
     private static final int IP_HEADER_OFFSET = ETH_HEADER_LEN;
 
@@ -1051,11 +1060,11 @@
     private static final int IPV6_NEXT_HEADER_OFFSET     = IP_HEADER_OFFSET + 6;
     private static final int IPV6_SRC_ADDR_OFFSET        = IP_HEADER_OFFSET + 8;
     private static final int IPV6_DEST_ADDR_OFFSET       = IP_HEADER_OFFSET + 24;
-    private static final int IPV6_TCP_HEADER_OFFSET      = IP_HEADER_OFFSET + IPV6_HEADER_LEN;
-    private static final int IPV6_TCP_SRC_PORT_OFFSET    = IPV6_TCP_HEADER_OFFSET + 0;
-    private static final int IPV6_TCP_DEST_PORT_OFFSET   = IPV6_TCP_HEADER_OFFSET + 2;
-    private static final int IPV6_TCP_SEQ_NUM_OFFSET     = IPV6_TCP_HEADER_OFFSET + 4;
-    private static final int IPV6_TCP_ACK_NUM_OFFSET     = IPV6_TCP_HEADER_OFFSET + 8;
+    private static final int IPV6_PAYLOAD_OFFSET = IP_HEADER_OFFSET + IPV6_HEADER_LEN;
+    private static final int IPV6_TCP_SRC_PORT_OFFSET    = IPV6_PAYLOAD_OFFSET + 0;
+    private static final int IPV6_TCP_DEST_PORT_OFFSET   = IPV6_PAYLOAD_OFFSET + 2;
+    private static final int IPV6_TCP_SEQ_NUM_OFFSET     = IPV6_PAYLOAD_OFFSET + 4;
+    private static final int IPV6_TCP_ACK_NUM_OFFSET     = IPV6_PAYLOAD_OFFSET + 8;
     // The IPv6 all nodes address ff02::1
     private static final byte[] IPV6_ALL_NODES_ADDRESS   =
             { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
@@ -1134,6 +1143,13 @@
     private static final byte[] BUG_PROBE_SOURCE_ADDR1   = {0, 0, 1, 2};
     private static final byte[] BUG_PROBE_SOURCE_ADDR2   = {3, 4, 0, 0};
     private static final byte[] IPV4_ANY_HOST_ADDR       = {0, 0, 0, 0};
+    private static final byte[] IPV4_MDNS_MULTICAST_ADDR = {(byte) 224, 0, 0, (byte) 251};
+    private static final byte[] IPV6_MDNS_MULTICAST_ADDR =
+            {(byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfb};
+    private static final int DNS_HEADER_LEN = 12;
+    private static final int DNS_QDCOUNT_OFFSET = 4;
+    private static final int IPV6_UDP_DEST_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 2;
+    private static final int MDNS_UDP_PORT = 5353;
 
     // Helper to initialize a default apfFilter.
     private ApfFilter setupApfFilter(
@@ -1267,6 +1283,98 @@
         apfFilter.shutdown();
     }
 
+    private static byte[] makeMdnsV4Packet(String qname) throws IOException {
+        final ByteBuffer buf = ByteBuffer.wrap(new byte[100]);
+        final PacketBuilder builder = new PacketBuilder(buf);
+        builder.writeL2Header(MacAddress.fromString("11:22:33:44:55:66"),
+                MacAddress.fromBytes(ETH_MULTICAST_MDNS_v4_MAC_ADDRESS),
+                (short) ETH_P_IP);
+        builder.writeIpv4Header((byte) 0 /* tos */, (short) 0 /* id */,
+                (short) 0 /* flagsAndFragmentOffset */, (byte) 0 /* ttl */, (byte) IPPROTO_UDP,
+                (Inet4Address) Inet4Address.getByAddress(IPV4_SOURCE_ADDR),
+                (Inet4Address) Inet4Address.getByAddress(IPV4_MDNS_MULTICAST_ADDR));
+        builder.writeUdpHeader((short) MDNS_UDP_PORT, (short) MDNS_UDP_PORT);
+        buf.put(new DnsPacket.DnsHeader(0, 0, 1, 0).getBytes());
+        buf.put(DnsPacket.DnsRecord.makeQuestion(qname, 0, 0).getBytes());
+        return builder.finalizePacket().array();
+    }
+
+    private static byte[] makeMdnsV6Packet(String qname) throws IOException {
+        ByteBuffer buf = ByteBuffer.wrap(new byte[100]);
+        final PacketBuilder builder = new PacketBuilder(buf);
+        builder.writeL2Header(MacAddress.fromString("11:22:33:44:55:66"),
+                MacAddress.fromBytes(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS),
+                (short) ETH_P_IPV6);
+        builder.writeIpv6Header(0x680515ca /* vtf */, (byte) IPPROTO_UDP, (short) 0 /* hopLimit */,
+                (Inet6Address) InetAddress.getByAddress(IPV6_ANOTHER_ADDR),
+                (Inet6Address) Inet6Address.getByAddress(IPV6_MDNS_MULTICAST_ADDR));
+        builder.writeUdpHeader((short) MDNS_UDP_PORT, (short) MDNS_UDP_PORT);
+        buf.put(new DnsPacket.DnsHeader(0, 0, 1, 0).getBytes());
+        buf.put(DnsPacket.DnsRecord.makeQuestion(qname, 0, 0).getBytes());
+        return builder.finalizePacket().array();
+    }
+
+    @Test
+    public void testQnameEncoding() {
+        String[] qname = new String[]{"abcd", "ef", "日本"};
+        byte[] encodedQname = ApfFilter.encodeQname(qname);
+        Assert.assertArrayEquals(
+                new byte[]{0x04, 0x61, 0x62, 0x63, 0x64, 0x02, 0x65, 0x66, 0x06, (byte) 0xe6,
+                        (byte) 0x97, (byte) 0xa5, (byte) 0xe6, (byte) 0x9c, (byte) 0xac, 0x00},
+                encodedQname);
+    }
+
+    @Test
+    public void testApfFilterMdns() throws Exception {
+        final byte[] unicastIpv4Addr = {(byte) 192, 0, 2, 63};
+
+        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+        LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24);
+        LinkProperties lp = new LinkProperties();
+        lp.addLinkAddress(link);
+
+        ApfConfiguration config = getDefaultConfig();
+        TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mLog);
+        apfFilter.setLinkProperties(lp);
+
+        // Construct IPv4 mDNS packet
+        byte[] mdnsv4packet = makeMdnsV4Packet("test.local");
+        byte[] mdnsv6packet = makeMdnsV6Packet("test.local");
+        byte[] program = ipClientCallback.getApfProgram();
+        // mDNSv4 packet is passed if no mDns filter is turned on
+        assertPass(program, mdnsv4packet);
+        // mDNSv6 packet is passed if no mDNS filter is turned on
+        assertPass(program, mdnsv6packet);
+
+        // mDNSv4 packet with qname in the allowlist is passed
+        apfFilter.addToMdnsAllowList(new String[]{"test", "local"});
+        apfFilter.addToMdnsAllowList(new String[]{"abcd", "local"});
+        apfFilter.setMulticastFilter(true);
+        program = ipClientCallback.getApfProgram();
+        assertPass(program, mdnsv4packet);
+        assertPass(program, mdnsv6packet);
+
+        mdnsv4packet = makeMdnsV4Packet("abcd.local");
+        mdnsv6packet = makeMdnsV6Packet("abcd.local");
+        assertPass(program, mdnsv4packet);
+        assertPass(program, mdnsv6packet);
+
+        // mDNSv4 packet with qname not in the allowlist is dropped
+        mdnsv4packet = makeMdnsV4Packet("ffff.local");
+        mdnsv6packet = makeMdnsV6Packet("ffff.local");
+        assertDrop(program, mdnsv4packet);
+        assertDrop(program, mdnsv6packet);
+
+        apfFilter.removeFromAllowList(new String[]{"abcd", "local"});
+        program = ipClientCallback.getApfProgram();
+        mdnsv4packet = makeMdnsV4Packet("abcd.local");
+        mdnsv6packet = makeMdnsV6Packet("abcd.local");
+        assertDrop(program, mdnsv4packet);
+        assertDrop(program, mdnsv6packet);
+
+        apfFilter.shutdown();
+    }
+
     @Test
     public void testApfFilterMulticast() throws Exception {
         final byte[] unicastIpv4Addr   = {(byte)192,0,2,63};