blob: 3756bb7b56b30b86bc26b91396d20e45e5b101a8 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.ip;
import static android.system.OsConstants.AF_UNIX;
import static android.system.OsConstants.SOCK_DGRAM;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.net.netlink.NetlinkSocket;
import android.net.util.SharedLog;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.system.ErrnoException;
import android.system.Os;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import libcore.util.HexEncoding;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
/**
* Tests for ConntrackMonitor.
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ConntrackMonitorTest {
private static final long TIMEOUT_MS = 10_000L;
@Mock private SharedLog mLog;
@Mock private ConntrackMonitor.ConntrackEventConsumer mConsumer;
private final HandlerThread mHandlerThread = new HandlerThread(
ConntrackMonitorTest.class.getSimpleName());
// Late init since the handler thread has been started.
private Handler mHandler;
private TestConntrackMonitor mConntrackMonitor;
// A version of [ConntrackMonitor] that reads packets from the socket pair, and instead
// allows the test to write test packets to the socket pair via [sendMessage].
private class TestConntrackMonitor extends ConntrackMonitor {
TestConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log,
@NonNull ConntrackEventConsumer cb) {
super(h, log, cb);
mReadFd = new FileDescriptor();
mWriteFd = new FileDescriptor();
try {
Os.socketpair(AF_UNIX, SOCK_DGRAM, 0, mWriteFd, mReadFd);
} catch (ErrnoException e) {
fail("Could not create socket pair: " + e);
}
}
@Override
protected FileDescriptor createFd() {
return mReadFd;
}
private void sendMessage(byte[] msg) {
mHandler.post(() -> {
try {
NetlinkSocket.sendMessage(mWriteFd, msg, 0 /* offset */, msg.length,
TIMEOUT_MS);
} catch (ErrnoException | InterruptedIOException e) {
fail("Unable to send netfilter message: " + e);
}
});
}
private final FileDescriptor mReadFd;
private final FileDescriptor mWriteFd;
}
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
// ConntrackMonitor needs to be started from the handler thread.
final ConditionVariable initDone = new ConditionVariable();
mHandler.post(() -> {
TestConntrackMonitor m = new TestConntrackMonitor(mHandler, mLog, mConsumer);
m.start();
mConntrackMonitor = m;
initDone.open();
});
if (!initDone.block(TIMEOUT_MS)) {
fail("... init monitor timed-out after " + TIMEOUT_MS + "ms");
}
}
@After
public void tearDown() throws Exception {
mHandlerThread.quitSafely();
}
// TODO: Add conntrack message attributes to have further verification.
public static final String CT_V4NEW_TCP_HEX =
// CHECKSTYLE:OFF IndentationCheck
// struct nlmsghdr
"8C000000" + // length = 140
"0001" + // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_NEW (0)
"0006" + // flags = NLM_F_CREATE (1 << 10) | NLM_F_EXCL (1 << 9)
"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=443 (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=443 (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
"0000019e" + // nla_value = 0b110011110 (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)
// 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_TCP_BYTES =
HexEncoding.decode(CT_V4NEW_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
@Test
public void testConntrackEvent_New() throws Exception {
mConntrackMonitor.sendMessage(CT_V4NEW_TCP_BYTES);
verify(mConsumer, timeout(TIMEOUT_MS)).accept(any() /* TODO: check the content */);
}
}