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

Commit dbc29516 authored by Hungming Chen's avatar Hungming Chen
Browse files

[NFCT.NS.11] Add NAT conntrack event notification

Test: atest TetheringCoverageTests

Change-Id: I804a0ba0ce53c43709598d77d4d7c5fa44a561ee
parent bb885a25
Loading
Loading
Loading
Loading
+106 −2
Original line number Diff line number Diff line
@@ -16,7 +16,11 @@

package android.net.ip;

import static android.net.netlink.ConntrackMessage.DYING_MASK;
import static android.net.netlink.ConntrackMessage.ESTABLISHED_MASK;

import android.net.netlink.ConntrackMessage;
import android.net.netlink.NetlinkConstants;
import android.net.netlink.NetlinkMessage;
import android.net.util.SharedLog;
import android.os.Handler;
@@ -24,6 +28,11 @@ import android.system.OsConstants;

import androidx.annotation.NonNull;

import com.android.internal.annotations.VisibleForTesting;

import java.util.Objects;


/**
 * ConntrackMonitor.
 *
@@ -45,7 +54,96 @@ public class ConntrackMonitor extends NetlinkMonitor {
    /**
     * A class for describing parsed netfilter conntrack events.
     */
    public static class ConntrackEvent { /*TODO*/ }
    public static class ConntrackEvent {
        /**
         * Conntrack event type.
         */
        public final short msgType;
        /**
         * Original direction conntrack tuple.
         */
        public final ConntrackMessage.Tuple tupleOrig;
        /**
         * Reply direction conntrack tuple.
         */
        public final ConntrackMessage.Tuple tupleReply;
        /**
         * Connection status. A bitmask of ip_conntrack_status enum flags.
         */
        public final int status;
        /**
         * Conntrack timeout.
         */
        public final int timeoutSec;

        public ConntrackEvent(ConntrackMessage msg) {
            this.msgType = msg.getHeader().nlmsg_type;
            this.tupleOrig = msg.tupleOrig;
            this.tupleReply = msg.tupleReply;
            this.status = msg.status;
            this.timeoutSec = msg.timeoutSec;
        }

        @VisibleForTesting
        public ConntrackEvent(short msgType, ConntrackMessage.Tuple tupleOrig,
                ConntrackMessage.Tuple tupleReply, int status, int timeoutSec) {
            this.msgType = msgType;
            this.tupleOrig = tupleOrig;
            this.tupleReply = tupleReply;
            this.status = status;
            this.timeoutSec = timeoutSec;
        }

        @Override
        @VisibleForTesting
        public boolean equals(Object o) {
            if (!(o instanceof ConntrackEvent)) return false;
            ConntrackEvent that = (ConntrackEvent) o;
            return this.msgType == that.msgType
                    && Objects.equals(this.tupleOrig, that.tupleOrig)
                    && Objects.equals(this.tupleReply, that.tupleReply)
                    && this.status == that.status
                    && this.timeoutSec == that.timeoutSec;
        }

        @Override
        public int hashCode() {
            return Objects.hash(msgType, tupleOrig, tupleReply, status, timeoutSec);
        }

        /**
         * Check the established NAT session conntrack message.
         *
         * @param msg the conntrack message to check.
         * @return true if an established NAT message, false if not.
         */
        public static boolean isEstablishedNatSession(@NonNull ConntrackMessage msg) {
            if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_NEW) return false;
            if (msg.tupleOrig == null) return false;
            if (msg.tupleReply == null) return false;
            if (msg.timeoutSec == 0) return false;
            if ((msg.status & ESTABLISHED_MASK) != ESTABLISHED_MASK) return false;

            return true;
        }

        /**
         * Check the dying NAT session conntrack message.
         * Note that IPCTNL_MSG_CT_DELETE event has no CTA_TIMEOUT attribute.
         *
         * @param msg the conntrack message to check.
         * @return true if a dying NAT message, false if not.
         */
        public static boolean isDyingNatSession(@NonNull ConntrackMessage msg) {
            if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_DELETE) return false;
            if (msg.tupleOrig == null) return false;
            if (msg.tupleReply == null) return false;
            if (msg.timeoutSec != 0) return false;
            if ((msg.status & DYING_MASK) != DYING_MASK) return false;

            return true;
        }
    }

    /**
     * A callback to caller for conntrack event.
@@ -74,6 +172,12 @@ public class ConntrackMonitor extends NetlinkMonitor {
            return;
        }

        mConsumer.accept(new ConntrackEvent() /* TODO */);
        final ConntrackMessage conntrackMsg = (ConntrackMessage) nlMsg;
        if (!(ConntrackEvent.isEstablishedNatSession(conntrackMsg)
                || ConntrackEvent.isDyingNatSession(conntrackMsg))) {
            return;
        }

        mConsumer.accept(new ConntrackEvent(conntrackMsg));
    }
}
+50 −0
Original line number Diff line number Diff line
@@ -30,10 +30,13 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.system.OsConstants;

import com.android.internal.annotations.VisibleForTesting;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Objects;


/**
@@ -65,6 +68,32 @@ public class ConntrackMessage extends NetlinkMessage {
    public static final short CTA_PROTO_SRC_PORT = 2;
    public static final short CTA_PROTO_DST_PORT = 3;

    // enum ip_conntrack_status
    public static final int IPS_EXPECTED      = 0x00000001;
    public static final int IPS_SEEN_REPLY    = 0x00000002;
    public static final int IPS_ASSURED       = 0x00000004;
    public static final int IPS_CONFIRMED     = 0x00000008;
    public static final int IPS_SRC_NAT       = 0x00000010;
    public static final int IPS_DST_NAT       = 0x00000020;
    public static final int IPS_SEQ_ADJUST    = 0x00000040;
    public static final int IPS_SRC_NAT_DONE  = 0x00000080;
    public static final int IPS_DST_NAT_DONE  = 0x00000100;
    public static final int IPS_DYING         = 0x00000200;
    public static final int IPS_FIXED_TIMEOUT = 0x00000400;
    public static final int IPS_TEMPLATE      = 0x00000800;
    public static final int IPS_UNTRACKED     = 0x00001000;
    public static final int IPS_HELPER        = 0x00002000;
    public static final int IPS_OFFLOAD       = 0x00004000;
    public static final int IPS_HW_OFFLOAD    = 0x00008000;

    // ip_conntrack_status mask
    // Interesting on the NAT conntrack session which has already seen two direction traffic.
    // TODO: Probably IPS_{SRC, DST}_NAT_DONE are also interesting.
    public static final int ESTABLISHED_MASK = IPS_CONFIRMED | IPS_ASSURED | IPS_SEEN_REPLY
            | IPS_SRC_NAT;
    // Interesting on the established NAT conntrack session which is dying.
    public static final int DYING_MASK = ESTABLISHED_MASK | IPS_DYING;

    /**
     * A tuple for the conntrack connection information.
     *
@@ -88,6 +117,23 @@ public class ConntrackMessage extends NetlinkMessage {
            this.dstPort = proto.dstPort;
            this.protoNum = proto.protoNum;
        }

        @Override
        @VisibleForTesting
        public boolean equals(Object o) {
            if (!(o instanceof Tuple)) return false;
            Tuple that = (Tuple) o;
            return Objects.equals(this.srcIp, that.srcIp)
                    && Objects.equals(this.dstIp, that.dstIp)
                    && this.srcPort == that.srcPort
                    && this.dstPort == that.dstPort
                    && this.protoNum == that.protoNum;
        }

        @Override
        public int hashCode() {
            return Objects.hash(srcIp, dstIp, srcPort, dstPort, protoNum);
        }
    }

    /**
@@ -400,4 +446,8 @@ public class ConntrackMessage extends NetlinkMessage {
        mHeader.pack(byteBuffer);
        nfGenMsg.pack(byteBuffer);
    }

    public short getMessageType() {
        return (short) (getHeader().nlmsg_type & ~(NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8));
    }
}
+133 −4
Original line number Diff line number Diff line
@@ -15,14 +15,25 @@
 */
package android.net.ip;

import static android.net.ip.ConntrackMonitor.ConntrackEvent;
import static android.net.netlink.ConntrackMessage.Tuple;
import static android.net.netlink.ConntrackMessage.TupleIpv4;
import static android.net.netlink.ConntrackMessage.TupleProto;
import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE;
import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW;
import static android.system.OsConstants.AF_UNIX;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.SOCK_DGRAM;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;

import android.net.InetAddresses;
import android.net.netlink.NetlinkConstants;
import android.net.netlink.NetlinkSocket;
import android.net.util.SharedLog;
import android.os.ConditionVariable;
@@ -46,6 +57,7 @@ import org.mockito.MockitoAnnotations;

import java.io.FileDescriptor;
import java.io.InterruptedIOException;
import java.net.Inet4Address;


/**
@@ -128,7 +140,6 @@ public class ConntrackMonitorTest {
        mHandlerThread.quitSafely();
    }

    // TODO: Add conntrack message attributes to have further verification.
    public static final String CT_V4NEW_TCP_HEX =
            // CHECKSTYLE:OFF IndentationCheck
            // struct nlmsghdr
@@ -184,9 +195,127 @@ public class ConntrackMonitorTest {
    public static final byte[] CT_V4NEW_TCP_BYTES =
            HexEncoding.decode(CT_V4NEW_TCP_HEX.replaceAll(" ", "").toCharArray(), false);

    @NonNull
    private ConntrackEvent makeTestConntrackEvent(short msgType, int status, int timeoutSec) {
        final Inet4Address privateIp =
                (Inet4Address) InetAddresses.parseNumericAddress("192.168.80.12");
        final Inet4Address remoteIp =
                (Inet4Address) InetAddresses.parseNumericAddress("140.112.8.116");
        final Inet4Address publicIp =
                (Inet4Address) InetAddresses.parseNumericAddress("100.81.179.1");

        return new ConntrackEvent(
                (short) (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 | msgType),
                new Tuple(new TupleIpv4(privateIp, remoteIp),
                        new TupleProto((byte) IPPROTO_TCP, (short) 62449, (short) 443)),
                new Tuple(new TupleIpv4(remoteIp, publicIp),
                        new TupleProto((byte) IPPROTO_TCP, (short) 443, (short) 62449)),
                status,
                timeoutSec);
    }

    @Test
    public void testConntrackEvent_New() throws Exception {
    public void testConntrackEventNew() throws Exception {
        final ConntrackEvent expectedEvent = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW,
                0x19e /* status */, 120 /* timeoutSec */);
        mConntrackMonitor.sendMessage(CT_V4NEW_TCP_BYTES);
        verify(mConsumer, timeout(TIMEOUT_MS)).accept(any() /* TODO: check the content */);
        verify(mConsumer, timeout(TIMEOUT_MS)).accept(eq(expectedEvent));
    }

    @Test
    public void testConntrackEventEquals() {
        final ConntrackEvent event1 = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
                5678 /* timeoutSec*/);
        final ConntrackEvent event2 = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
                5678 /* timeoutSec*/);
        assertEquals(event1, event2);
    }

    @Test
    public void testConntrackEventNotEquals() {
        final ConntrackEvent e = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
                5678 /* timeoutSec*/);

        final ConntrackEvent typeNotEqual = new ConntrackEvent((short) (e.msgType + 1) /* diff */,
                e.tupleOrig, e.tupleReply, e.status, e.timeoutSec);
        assertNotEquals(e, typeNotEqual);

        final ConntrackEvent tupleOrigNotEqual = new ConntrackEvent(e.msgType,
                null /* diff */, e.tupleReply, e.status, e.timeoutSec);
        assertNotEquals(e, tupleOrigNotEqual);

        final ConntrackEvent tupleReplyNotEqual = new ConntrackEvent(e.msgType,
                e.tupleOrig, null /* diff */, e.status, e.timeoutSec);
        assertNotEquals(e, tupleReplyNotEqual);

        final ConntrackEvent statusNotEqual = new ConntrackEvent(e.msgType,
                e.tupleOrig, e.tupleReply, e.status + 1 /* diff */, e.timeoutSec);
        assertNotEquals(e, statusNotEqual);

        final ConntrackEvent timeoutSecNotEqual = new ConntrackEvent(e.msgType,
                e.tupleOrig, e.tupleReply, e.status, e.timeoutSec + 1 /* diff */);
        assertNotEquals(e, timeoutSecNotEqual);
    }

    public static final String CT_V4DELETE_TCP_HEX =
            // CHECKSTYLE:OFF IndentationCheck
            // struct nlmsghdr
            "84000000" +      // length = 132
            "0201" +          // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_DELETE (2)
            "0000" +          // flags = 0
            "00000000" +      // seqno = 0
            "00000000" +      // pid = 0
            // struct nfgenmsg
            "02" +            // nfgen_family  = AF_INET
            "00" +            // version = NFNETLINK_V0
            "1234" +          // res_id = 0x1234 (big endian)
            // struct nlattr
            "3400" +          // nla_len = 52
            "0180" +          // nla_type = nested CTA_TUPLE_ORIG
                // struct nlattr
                "1400" +      // nla_len = 20
                "0180" +      // nla_type = nested CTA_TUPLE_IP
                    "0800 0100 C0A8500C" +  // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12
                    "0800 0200 8C700874" +  // nla_type=CTA_IP_V4_DST, ip=140.112.8.116
                // struct nlattr
                "1C00" +      // nla_len = 28
                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
                    "0600 0200 F3F1 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian)
                    "0600 0300 01BB 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=433 (big endian)
            // struct nlattr
            "3400" +          // nla_len = 52
            "0280" +          // nla_type = nested CTA_TUPLE_REPLY
                // struct nlattr
                "1400" +      // nla_len = 20
                "0180" +      // nla_type = nested CTA_TUPLE_IP
                    "0800 0100 8C700874" +  // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116
                    "0800 0200 6451B301" +  // nla_type=CTA_IP_V4_DST, ip=100.81.179.1
                // struct nlattr
                "1C00" +      // nla_len = 28
                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
                    "0600 0200 01BB 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=433 (big endian)
                    "0600 0300 F3F1 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian)
            // struct nlattr
            "0800" +          // nla_len = 8
            "0300" +          // nla_type = CTA_STATUS
            "0000039E";       // nla_value = 0b1110011110 (big endian)
                              // IPS_SEEN_REPLY (1 << 1) | IPS_ASSURED (1 << 2) |
                              // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) |
                              // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8) |
                              // IPS_DYING (1 << 9)
            // CHECKSTYLE:ON IndentationCheck
    public static final byte[] CT_V4DELETE_TCP_BYTES =
            HexEncoding.decode(CT_V4DELETE_TCP_HEX.replaceAll(" ", "").toCharArray(), false);

    @Test
    public void testConntrackEventDelete() throws Exception {
        final ConntrackEvent expectedEvent =
                makeTestConntrackEvent(IPCTNL_MSG_CT_DELETE, 0x39e /* status */,
                        0 /* timeoutSec (absent) */);
        mConntrackMonitor.sendMessage(CT_V4DELETE_TCP_BYTES);
        verify(mConsumer, timeout(TIMEOUT_MS)).accept(eq(expectedEvent));
    }

}