Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7d21eaed authored by Hugo Benichi's avatar Hugo Benichi
Browse files

ApfFilter: take into account IPv4 subnet prefix

When IPv4 is provisioned on an interface with Apf capabilities,
ApfFilter will only keep track of the raw ipv4 address, with no
information about the subnet or prefix length.

This patch adds the missing prefix length information to ApfFilter. This
allows to calculate the subnet broadcast ipv4 address for more
precise ipv4 broadcast filtering when the multicast lock is not held.

Bug: 30231088

Change-Id: Iebaec040703647c4ced30bb585be173e97a1fae5
parent 0dc1d314
Loading
Loading
Loading
Loading
+63 −35
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.net.apf;
import static android.system.OsConstants.*;

import android.os.SystemClock;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkUtils;
import android.net.apf.ApfGenerator;
@@ -44,6 +45,7 @@ import com.android.internal.util.IndentingPrintWriter;
import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.Thread;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
@@ -230,6 +232,9 @@ public class ApfFilter {
    // Our IPv4 address, if we have just one, otherwise null.
    @GuardedBy("this")
    private byte[] mIPv4Address;
    // The subnet prefix length of our IPv4 network. Only valid if mIPv4Address is not null.
    @GuardedBy("this")
    private int mIPv4PrefixLength;

    @VisibleForTesting
    ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
@@ -365,26 +370,6 @@ public class ApfFilter {

        // Can't be static because it's in a non-static inner class.
        // TODO: Make this static once RA is its own class.
        private int uint8(byte b) {
            return b & 0xff;
        }

        private int uint16(short s) {
            return s & 0xffff;
        }

        private long uint32(int i) {
            return i & 0xffffffffL;
        }

        private long getUint16(ByteBuffer buffer, int position) {
            return uint16(buffer.getShort(position));
        }

        private long getUint32(ByteBuffer buffer, int position) {
            return uint32(buffer.getInt(position));
        }

        private void prefixOptionToString(StringBuffer sb, int offset) {
            String prefix = IPv6AddresstoString(offset + 16);
            int length = uint8(mPacket.get(offset + 2));
@@ -780,7 +765,10 @@ public class ApfFilter {
            // If IPv4 broadcast packet, drop regardless of L2 (b/30231088).
            gen.addLoad32(Register.R0, IPV4_DEST_ADDR_OFFSET);
            gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, gen.DROP_LABEL);
            // TODO: also filter subnet broadcast address.
            if (mIPv4Address != null && mIPv4PrefixLength < 31) {
                int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength);
                gen.addJumpIfR0Equals(broadcastAddr, gen.DROP_LABEL);
            }

            // If L2 broadcast packet, drop.
            gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
@@ -1078,26 +1066,32 @@ public class ApfFilter {
        }
    }

    // Find the single IPv4 address if there is one, otherwise return null.
    private static byte[] findIPv4Address(LinkProperties lp) {
        byte[] ipv4Address = null;
        for (InetAddress inetAddr : lp.getAddresses()) {
            byte[] addr = inetAddr.getAddress();
            if (addr.length != 4) continue;
            // More than one IPv4 address, abort
            if (ipv4Address != null && !Arrays.equals(ipv4Address, addr)) return null;
            ipv4Address = addr;
    /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */
    private static LinkAddress findIPv4LinkAddress(LinkProperties lp) {
        LinkAddress ipv4Address = null;
        for (LinkAddress address : lp.getLinkAddresses()) {
            if (!(address.getAddress() instanceof Inet4Address)) {
                continue;
            }
            if (ipv4Address != null && !ipv4Address.isSameAddressAs(address)) {
                // More than one IPv4 address, abort.
                return null;
            }
            ipv4Address = address;
        }
        return ipv4Address;
    }

    public synchronized void setLinkProperties(LinkProperties lp) {
        // NOTE: Do not keep a copy of LinkProperties as it would further duplicate state.
        byte[] ipv4Address = findIPv4Address(lp);
        // If ipv4Address is the same as mIPv4Address, then there's no change, just return.
        if (Arrays.equals(ipv4Address, mIPv4Address)) return;
        // Otherwise update mIPv4Address and install new program.
        mIPv4Address = ipv4Address;
        final LinkAddress ipv4Address = findIPv4LinkAddress(lp);
        final byte[] addr = (ipv4Address != null) ? ipv4Address.getAddress().getAddress() : null;
        final int prefix = (ipv4Address != null) ? ipv4Address.getPrefixLength() : 0;
        if ((prefix == mIPv4PrefixLength) && Arrays.equals(addr, mIPv4Address)) {
            return;
        }
        mIPv4Address = addr;
        mIPv4PrefixLength = prefix;
        installNewProgramLocked();
    }

@@ -1143,4 +1137,38 @@ public class ApfFilter {
            pw.decreaseIndent();
        }
    }

    private static int uint8(byte b) {
        return b & 0xff;
    }

    private static int uint16(short s) {
        return s & 0xffff;
    }

    private static long uint32(int i) {
        return i & 0xffffffffL;
    }

    private static long getUint16(ByteBuffer buffer, int position) {
        return uint16(buffer.getShort(position));
    }

    private static long getUint32(ByteBuffer buffer, int position) {
        return uint32(buffer.getInt(position));
    }

    // TODO: move to android.net.NetworkUtils
    @VisibleForTesting
    public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) {
        return bytesToInt(addrBytes) | (int) (uint32(-1) >>> prefixLength);
    }

    @VisibleForTesting
    public static int bytesToInt(byte[] addrBytes) {
        return (uint8(addrBytes[0]) << 24)
                + (uint8(addrBytes[1]) << 16)
                + (uint8(addrBytes[2]) << 8)
                + (uint8(addrBytes[3]));
    }
}
+49 −11
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@ import static android.system.OsConstants.*;

import com.android.frameworks.servicestests.R;

import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkUtils;
import android.net.apf.ApfCapabilities;
import android.net.apf.ApfFilter;
import android.net.apf.ApfGenerator;
@@ -28,8 +31,6 @@ import android.net.apf.ApfGenerator.Register;
import android.net.ip.IpManager;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.RaEvent;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.os.ConditionVariable;
import android.os.Parcelable;
import android.system.ErrnoException;
@@ -713,7 +714,7 @@ public class ApfTest extends AndroidTestCase {
    private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24;

    private static final byte[] MOCK_IPV4_ADDR           = {10, 0, 0, 1};
    private static final byte[] MOCK_BROADCAST_IPV4_ADDR = {10, 0, (byte) 255, (byte) 255};
    private static final byte[] MOCK_BROADCAST_IPV4_ADDR = {10, 0, 31, (byte) 255}; // prefix = 19
    private static final byte[] MOCK_MULTICAST_IPV4_ADDR = {(byte) 224, 0, 0, 1};
    private static final byte[] ANOTHER_IPV4_ADDR        = {10, 0, 0, 2};
    private static final byte[] IPV4_ANY_HOST_ADDR       = {0, 0, 0, 0};
@@ -721,8 +722,12 @@ public class ApfTest extends AndroidTestCase {
    @LargeTest
    public void testApfFilterIPv4() throws Exception {
        MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
        LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
        LinkProperties lp = new LinkProperties();
        lp.addLinkAddress(link);

        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, mLog);
        apfFilter.setLinkProperties(lp);

        byte[] program = ipManagerCallback.getApfProgram();

@@ -740,7 +745,7 @@ public class ApfTest extends AndroidTestCase {
        put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
        assertDrop(program, packet.array());
        put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
        assertPass(program, packet.array());
        assertDrop(program, packet.array());

        // Verify multicast/broadcast IPv4, not DHCP to us, is dropped
        put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
@@ -797,15 +802,21 @@ public class ApfTest extends AndroidTestCase {

    @LargeTest
    public void testApfFilterMulticast() throws Exception {
        MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog);
        byte[] program = ipManagerCallback.getApfProgram();

        final byte[] unicastIpv4Addr   = {(byte)192,0,2,63};
        final byte[] broadcastIpv4Addr = {(byte)192,0,(byte)255,(byte)255};
        final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
        final byte[] multicastIpv4Addr = {(byte)224,0,0,1};
        final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};

        MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
        LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24);
        LinkProperties lp = new LinkProperties();
        lp.addLinkAddress(link);

        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, mLog);
        apfFilter.setLinkProperties(lp);

        byte[] program = ipManagerCallback.getApfProgram();

        // Construct IPv4 and IPv6 multicast packets.
        ByteBuffer mcastv4packet = ByteBuffer.wrap(new byte[100]);
        mcastv4packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
@@ -848,7 +859,7 @@ public class ApfTest extends AndroidTestCase {
        assertDrop(program, mcastv6packet.array());
        assertDrop(program, bcastv4packet1.array());
        assertDrop(program, bcastv4packet2.array());
        assertPass(program, bcastv4unicastl2packet.array());
        assertDrop(program, bcastv4unicastl2packet.array());

        // Turn off multicast filter and verify it's off
        ipManagerCallback.resetApfProgramWait();
@@ -864,11 +875,12 @@ public class ApfTest extends AndroidTestCase {
        ipManagerCallback.resetApfProgramWait();
        apfFilter.shutdown();
        apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, mLog);
        apfFilter.setLinkProperties(lp);
        program = ipManagerCallback.getApfProgram();
        assertDrop(program, mcastv4packet.array());
        assertDrop(program, mcastv6packet.array());
        assertDrop(program, bcastv4packet1.array());
        assertPass(program, bcastv4unicastl2packet.array());
        assertDrop(program, bcastv4unicastl2packet.array());

        // Verify that ICMPv6 multicast is not dropped.
        mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
@@ -1154,4 +1166,30 @@ public class ApfTest extends AndroidTestCase {
     */
    private native static boolean compareBpfApf(String filter, String pcap_filename,
            byte[] apf_program);

    public void testBytesToInt() {
        assertEquals(0x00000000, ApfFilter.bytesToInt(IPV4_ANY_HOST_ADDR));
        assertEquals(0xffffffff, ApfFilter.bytesToInt(IPV4_BROADCAST_ADDRESS));
        assertEquals(0x0a000001, ApfFilter.bytesToInt(MOCK_IPV4_ADDR));
        assertEquals(0x0a000002, ApfFilter.bytesToInt(ANOTHER_IPV4_ADDR));
        assertEquals(0x0a001fff, ApfFilter.bytesToInt(MOCK_BROADCAST_IPV4_ADDR));
        assertEquals(0xe0000001, ApfFilter.bytesToInt(MOCK_MULTICAST_IPV4_ADDR));
    }

    public void testBroadcastAddress() throws Exception {
        assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 0));
        assertEqualsIp("0.0.0.0", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 32));
        assertEqualsIp("0.0.3.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 22));
        assertEqualsIp("0.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 8));

        assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 0));
        assertEqualsIp("10.0.0.1", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 32));
        assertEqualsIp("10.0.0.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 24));
        assertEqualsIp("10.0.255.255", ApfFilter.ipv4BroadcastAddress(MOCK_IPV4_ADDR, 16));
    }

    public void assertEqualsIp(String expected, int got) throws Exception {
        int want = ApfFilter.bytesToInt(InetAddress.getByName(expected).getAddress());
        assertEquals(want, got);
    }
}