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

Commit 9140fe3b authored by Hungming Chen's avatar Hungming Chen Committed by Nucca Chen
Browse files

[NFCT.NS.8] Parse conntrack attribute CTA_STATUS and CTA_TIMEOUT

Parse CTA_STATUS and CTA_TIMEOUT for the IPv4 BPF tethering offload.
More attributes need to be parsed in the follow-up commits.

Test: atest TetheringCoverageTest
Change-Id: Ic2080d33d0517ef81ce92e189506fb1bb2425003
parent 5ca706f9
Loading
Loading
Loading
Loading
+59 −14
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;

import static java.nio.ByteOrder.BIG_ENDIAN;

import android.annotation.NonNull;
import android.system.OsConstants;

import java.net.Inet4Address;
@@ -42,6 +43,7 @@ public class ConntrackMessage extends NetlinkMessage {
    // enum ctattr_type
    public static final short CTA_TUPLE_ORIG  = 1;
    public static final short CTA_TUPLE_REPLY = 2;
    public static final short CTA_STATUS      = 3;
    public static final short CTA_TIMEOUT     = 7;

    // enum ctattr_tuple
@@ -105,35 +107,78 @@ public class ConntrackMessage extends NetlinkMessage {
        // Just build the netlink header and netfilter header for now and pretend the whole message
        // was consumed.
        // TODO: Parse the conntrack attributes.
        final ConntrackMessage conntrackMsg = new ConntrackMessage(header);
        final StructNfGenMsg nfGenMsg = StructNfGenMsg.parse(byteBuffer);
        if (nfGenMsg == null) {
            return null;
        }

        final int baseOffset = byteBuffer.position();
        StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(CTA_STATUS, byteBuffer);
        int status = 0;
        if (nlAttr != null) {
            status = nlAttr.getValueAsBe32(0);
        }

        conntrackMsg.mNfGenMsg = StructNfGenMsg.parse(byteBuffer);
        if (conntrackMsg.mNfGenMsg == null) {
        byteBuffer.position(baseOffset);
        nlAttr = StructNlAttr.findNextAttrOfType(CTA_TIMEOUT, byteBuffer);
        int timeoutSec = 0;
        if (nlAttr != null) {
            timeoutSec = nlAttr.getValueAsBe32(0);
        }

        // Advance to the end of the message.
        byteBuffer.position(baseOffset);
        final int kMinConsumed = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
        final int kAdditionalSpace = NetlinkConstants.alignedLengthOf(
                header.nlmsg_len - kMinConsumed);
        if (byteBuffer.remaining() < kAdditionalSpace) {
            return null;
        }
        byteBuffer.position(baseOffset + kAdditionalSpace);

        byteBuffer.position(byteBuffer.limit());
        return conntrackMsg;
        return new ConntrackMessage(header, nfGenMsg, status, timeoutSec);
    }

    protected StructNfGenMsg mNfGenMsg;
    /**
     * Netfilter header.
     */
    public final StructNfGenMsg nfGenMsg;
    /**
     * Connection status. A bitmask of ip_conntrack_status enum flags.
     *
     * The status is determined by the parsed attribute value CTA_STATUS, or 0 if the status could
     * not be parsed successfully (for example, if it was truncated or absent). For the message
     * from kernel, the valid status is non-zero. For the message from user space, the status may
     * be 0 (absent).
     */
    public final int status;
    /**
     * Conntrack timeout.
     *
     * The timeout is determined by the parsed attribute value CTA_TIMEOUT, or 0 if the timeout
     * could not be parsed successfully (for example, if it was truncated or absent). For
     * IPCTNL_MSG_CT_NEW event, the valid timeout is non-zero. For IPCTNL_MSG_CT_DELETE event, the
     * timeout is 0 (absent).
     */
    public final int timeoutSec;

    private ConntrackMessage() {
        super(new StructNlMsgHdr());
        mNfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET);
        nfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET);
        status = 0;
        timeoutSec = 0;
    }

    private ConntrackMessage(StructNlMsgHdr header) {
    private ConntrackMessage(@NonNull StructNlMsgHdr header, @NonNull StructNfGenMsg nfGenMsg,
            int status, int timeoutSec) {
        super(header);
        mNfGenMsg = null;
    }

    public StructNfGenMsg getNfHeader() {
        return mNfGenMsg;
        this.nfGenMsg = nfGenMsg;
        this.status = status;
        this.timeoutSec = timeoutSec;
    }

    public void pack(ByteBuffer byteBuffer) {
        mHeader.pack(byteBuffer);
        mNfGenMsg.pack(byteBuffer);
        nfGenMsg.pack(byteBuffer);
    }
}
+4 −21
Original line number Diff line number Diff line
@@ -48,23 +48,6 @@ public class RtNetlinkNeighborMessage extends NetlinkMessage {
    public static final short NDA_IFINDEX   = 8;
    public static final short NDA_MASTER    = 9;

    private static StructNlAttr findNextAttrOfType(short attrType, ByteBuffer byteBuffer) {
        while (byteBuffer != null && byteBuffer.remaining() > 0) {
            final StructNlAttr nlAttr = StructNlAttr.peek(byteBuffer);
            if (nlAttr == null) {
                break;
            }
            if (nlAttr.nla_type == attrType) {
                return StructNlAttr.parse(byteBuffer);
            }
            if (byteBuffer.remaining() < nlAttr.getAlignedLength()) {
                break;
            }
            byteBuffer.position(byteBuffer.position() + nlAttr.getAlignedLength());
        }
        return null;
    }

    public static RtNetlinkNeighborMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) {
        final RtNetlinkNeighborMessage neighMsg = new RtNetlinkNeighborMessage(header);

@@ -75,25 +58,25 @@ public class RtNetlinkNeighborMessage extends NetlinkMessage {

        // Some of these are message-type dependent, and not always present.
        final int baseOffset = byteBuffer.position();
        StructNlAttr nlAttr = findNextAttrOfType(NDA_DST, byteBuffer);
        StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(NDA_DST, byteBuffer);
        if (nlAttr != null) {
            neighMsg.mDestination = nlAttr.getValueAsInetAddress();
        }

        byteBuffer.position(baseOffset);
        nlAttr = findNextAttrOfType(NDA_LLADDR, byteBuffer);
        nlAttr = StructNlAttr.findNextAttrOfType(NDA_LLADDR, byteBuffer);
        if (nlAttr != null) {
            neighMsg.mLinkLayerAddr = nlAttr.nla_value;
        }

        byteBuffer.position(baseOffset);
        nlAttr = findNextAttrOfType(NDA_PROBES, byteBuffer);
        nlAttr = StructNlAttr.findNextAttrOfType(NDA_PROBES, byteBuffer);
        if (nlAttr != null) {
            neighMsg.mNumProbes = nlAttr.getValueAsInt(0);
        }

        byteBuffer.position(baseOffset);
        nlAttr = findNextAttrOfType(NDA_CACHEINFO, byteBuffer);
        nlAttr = StructNlAttr.findNextAttrOfType(NDA_CACHEINFO, byteBuffer);
        if (nlAttr != null) {
            neighMsg.mCacheInfo = StructNdaCacheInfo.parse(nlAttr.getValueAsByteBuffer());
        }
+4 −1
Original line number Diff line number Diff line
@@ -79,8 +79,11 @@ public class StructNfGenMsg {
    public void pack(ByteBuffer byteBuffer) {
        byteBuffer.put(nfgen_family);
        byteBuffer.put(version);
        // TODO: probably need to handle the little endian case.

        final ByteOrder originalOrder = byteBuffer.order();
        byteBuffer.order(ByteOrder.BIG_ENDIAN);
        byteBuffer.putShort(res_id);
        byteBuffer.order(originalOrder);
    }

    private static boolean hasAvailableSpace(@NonNull ByteBuffer byteBuffer) {
+29 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.net.netlink;

import androidx.annotation.Nullable;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
@@ -85,6 +87,33 @@ public class StructNlAttr {
        return struct;
    }

    /**
     * Find next netlink attribute with a given type from {@link ByteBuffer}.
     *
     * @param attrType The given netlink attribute type is requested for.
     * @param byteBuffer The buffer from which to find the netlink attribute.
     * @return the found netlink attribute, or {@code null} if the netlink attribute could not be
     *         found or parsed successfully (for example, if it was truncated).
     */
    @Nullable
    public static StructNlAttr findNextAttrOfType(short attrType,
            @Nullable ByteBuffer byteBuffer) {
        while (byteBuffer != null && byteBuffer.remaining() > 0) {
            final StructNlAttr nlAttr = StructNlAttr.peek(byteBuffer);
            if (nlAttr == null) {
                break;
            }
            if (nlAttr.nla_type == attrType) {
                return StructNlAttr.parse(byteBuffer);
            }
            if (byteBuffer.remaining() < nlAttr.getAlignedLength()) {
                break;
            }
            byteBuffer.position(byteBuffer.position() + nlAttr.getAlignedLength());
        }
        return null;
    }

    public short nla_len = (short) NLA_HEADERLEN;
    public short nla_type;
    public byte[] nla_value;
+57 −20
Original line number Diff line number Diff line
@@ -82,6 +82,14 @@ public class ConntrackMessageTest {
    public static final byte[] CT_V4UPDATE_TCP_BYTES =
            HexEncoding.decode(CT_V4UPDATE_TCP_HEX.replaceAll(" ", "").toCharArray(), false);

    private byte[] makeIPv4TimeoutUpdateRequestTcp() throws Exception {
        return ConntrackMessage.newIPv4TimeoutUpdateRequest(
                OsConstants.IPPROTO_TCP,
                (Inet4Address) InetAddress.getByName("192.168.43.209"), 44333,
                (Inet4Address) InetAddress.getByName("23.211.13.26"), 443,
                432000);
    }

    // Example 2: UDP (100.96.167.146, 37069) -> (216.58.197.10, 443)
    public static final String CT_V4UPDATE_UDP_HEX =
            // struct nlmsghdr
@@ -115,17 +123,27 @@ public class ConntrackMessageTest {
    public static final byte[] CT_V4UPDATE_UDP_BYTES =
            HexEncoding.decode(CT_V4UPDATE_UDP_HEX.replaceAll(" ", "").toCharArray(), false);

    private byte[] makeIPv4TimeoutUpdateRequestUdp() throws Exception {
        return ConntrackMessage.newIPv4TimeoutUpdateRequest(
                OsConstants.IPPROTO_UDP,
                (Inet4Address) InetAddress.getByName("100.96.167.146"), 37069,
                (Inet4Address) InetAddress.getByName("216.58.197.10"), 443,
                180);
    }

    @Test
    public void testConntrackIPv4TcpTimeoutUpdate() throws Exception {
    public void testConntrackMakeIPv4TcpTimeoutUpdate() throws Exception {
        assumeTrue(USING_LE);

        final byte[] tcp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
                OsConstants.IPPROTO_TCP,
                (Inet4Address) InetAddress.getByName("192.168.43.209"), 44333,
                (Inet4Address) InetAddress.getByName("23.211.13.26"), 443,
                432000);
        final byte[] tcp = makeIPv4TimeoutUpdateRequestTcp();
        assertArrayEquals(CT_V4UPDATE_TCP_BYTES, tcp);
    }

    @Test
    public void testConntrackParseIPv4TcpTimeoutUpdate() throws Exception {
        assumeTrue(USING_LE);

        final byte[] tcp = makeIPv4TimeoutUpdateRequestTcp();
        final ByteBuffer byteBuffer = ByteBuffer.wrap(tcp);
        byteBuffer.order(ByteOrder.nativeOrder());
        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER);
@@ -142,26 +160,30 @@ public class ConntrackMessageTest {
        assertEquals(1, hdr.nlmsg_seq);
        assertEquals(0, hdr.nlmsg_pid);

        final StructNfGenMsg nfmsgHdr = conntrackMessage.getNfHeader();
        final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg;
        assertNotNull(nfmsgHdr);
        assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family);
        assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
        assertEquals((short) 0, nfmsgHdr.res_id);

        // TODO: Parse the CTA_TUPLE_ORIG and CTA_TIMEOUT.
        // TODO: Parse the CTA_TUPLE_ORIG.
        assertEquals(0 /* absent */, conntrackMessage.status);
        assertEquals(432000, conntrackMessage.timeoutSec);
    }

    @Test
    public void testConntrackIPv4UdpTimeoutUpdate() throws Exception {
    public void testConntrackMakeIPv4UdpTimeoutUpdate() throws Exception {
        assumeTrue(USING_LE);

        final byte[] udp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
                OsConstants.IPPROTO_UDP,
                (Inet4Address) InetAddress.getByName("100.96.167.146"), 37069,
                (Inet4Address) InetAddress.getByName("216.58.197.10"), 443,
                180);
        final byte[] udp = makeIPv4TimeoutUpdateRequestUdp();
        assertArrayEquals(CT_V4UPDATE_UDP_BYTES, udp);
    }

    @Test
    public void testConntrackParseIPv4UdpTimeoutUpdate() throws Exception {
        assumeTrue(USING_LE);

        final byte[] udp = makeIPv4TimeoutUpdateRequestUdp();
        final ByteBuffer byteBuffer = ByteBuffer.wrap(udp);
        byteBuffer.order(ByteOrder.nativeOrder());
        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER);
@@ -178,20 +200,22 @@ public class ConntrackMessageTest {
        assertEquals(1, hdr.nlmsg_seq);
        assertEquals(0, hdr.nlmsg_pid);

        final StructNfGenMsg nfmsgHdr = conntrackMessage.getNfHeader();
        final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg;
        assertNotNull(nfmsgHdr);
        assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family);
        assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
        assertEquals((short) 0, nfmsgHdr.res_id);

        // TODO: Parse the CTA_TUPLE_ORIG and CTA_TIMEOUT.
        // TODO: Parse the CTA_TUPLE_ORIG.
        assertEquals(0 /* absent */, conntrackMessage.status);
        assertEquals(180, conntrackMessage.timeoutSec);
    }

    // TODO: Add conntrack message attributes to have further verification.
    public static final String CT_V4NEW_HEX =
            // CHECKSTYLE:OFF IndentationCheck
            // struct nlmsghdr
            "14000000" +      // length = 20
            "24000000" +      // length = 36
            "0001" +          // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_NEW (0)
            "0006" +          // flags = NLM_F_CREATE | NLM_F_EXCL
            "00000000" +      // seqno = 0
@@ -199,7 +223,17 @@ public class ConntrackMessageTest {
            // struct nfgenmsg
            "02" +            // nfgen_family = AF_INET
            "00" +            // version = NFNETLINK_V0
            "1234";           // res_id = 0x1234 (big endian)
            "1234" +          // res_id = 0x1234 (big endian)
            // struct nlattr
            "0800" +          // nla_len = 8
            "0300" +          // nla_type = CTA_STATUS
            "00000198" +      // nla_value = 0b110011000 (big endian)
                              // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) |
                              // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8)
            // struct nlattr
            "0800" +          // nla_len = 8
            "0700" +          // nla_type = CTA_TIMEOUT
            "00000078";       // nla_value = 120 (big endian)
            // CHECKSTYLE:ON IndentationCheck
    public static final byte[] CT_V4NEW_BYTES =
            HexEncoding.decode(CT_V4NEW_HEX.replaceAll(" ", "").toCharArray(), false);
@@ -217,17 +251,20 @@ public class ConntrackMessageTest {

        final StructNlMsgHdr hdr = conntrackMessage.getHeader();
        assertNotNull(hdr);
        assertEquals(20, hdr.nlmsg_len);
        assertEquals(36, hdr.nlmsg_len);
        assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type);
        assertEquals((short) (StructNlMsgHdr.NLM_F_CREATE | StructNlMsgHdr.NLM_F_EXCL),
                hdr.nlmsg_flags);
        assertEquals(0, hdr.nlmsg_seq);
        assertEquals(0, hdr.nlmsg_pid);

        final StructNfGenMsg nfmsgHdr = conntrackMessage.getNfHeader();
        final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg;
        assertNotNull(nfmsgHdr);
        assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family);
        assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
        assertEquals((short) 0x1234, nfmsgHdr.res_id);

        assertEquals(0x198, conntrackMessage.status);
        assertEquals(120, conntrackMessage.timeoutSec);
    }
}