Merge "[DK1]Add TCP polling mechanism"
This commit is contained in:
@@ -33,15 +33,27 @@ import static android.net.SocketKeepalive.MAX_INTERVAL_SEC;
|
|||||||
import static android.net.SocketKeepalive.MIN_INTERVAL_SEC;
|
import static android.net.SocketKeepalive.MIN_INTERVAL_SEC;
|
||||||
import static android.net.SocketKeepalive.NO_KEEPALIVE;
|
import static android.net.SocketKeepalive.NO_KEEPALIVE;
|
||||||
import static android.net.SocketKeepalive.SUCCESS;
|
import static android.net.SocketKeepalive.SUCCESS;
|
||||||
|
import static android.system.OsConstants.AF_INET;
|
||||||
|
import static android.system.OsConstants.AF_INET6;
|
||||||
|
import static android.system.OsConstants.SOL_SOCKET;
|
||||||
|
import static android.system.OsConstants.SO_SNDTIMEO;
|
||||||
|
|
||||||
|
import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
|
||||||
|
import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
|
||||||
|
import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
|
||||||
|
import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.net.ConnectivityResources;
|
import android.net.ConnectivityResources;
|
||||||
|
import android.net.INetd;
|
||||||
import android.net.ISocketKeepaliveCallback;
|
import android.net.ISocketKeepaliveCallback;
|
||||||
import android.net.InetAddresses;
|
import android.net.InetAddresses;
|
||||||
import android.net.InvalidPacketException;
|
import android.net.InvalidPacketException;
|
||||||
import android.net.KeepalivePacketData;
|
import android.net.KeepalivePacketData;
|
||||||
|
import android.net.MarkMaskParcel;
|
||||||
import android.net.NattKeepalivePacketData;
|
import android.net.NattKeepalivePacketData;
|
||||||
import android.net.NetworkAgent;
|
import android.net.NetworkAgent;
|
||||||
import android.net.SocketKeepalive.InvalidSocketException;
|
import android.net.SocketKeepalive.InvalidSocketException;
|
||||||
@@ -55,18 +67,29 @@ import android.os.Process;
|
|||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.system.ErrnoException;
|
import android.system.ErrnoException;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
|
import android.system.StructTimeval;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import com.android.connectivity.resources.R;
|
import com.android.connectivity.resources.R;
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.util.IndentingPrintWriter;
|
import com.android.internal.util.IndentingPrintWriter;
|
||||||
import com.android.net.module.util.HexDump;
|
import com.android.net.module.util.HexDump;
|
||||||
import com.android.net.module.util.IpUtils;
|
import com.android.net.module.util.IpUtils;
|
||||||
|
import com.android.net.module.util.SocketUtils;
|
||||||
|
import com.android.net.module.util.netlink.InetDiagMessage;
|
||||||
|
import com.android.net.module.util.netlink.NetlinkUtils;
|
||||||
|
import com.android.net.module.util.netlink.StructNlAttr;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.nio.BufferUnderflowException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -84,6 +107,7 @@ public class KeepaliveTracker {
|
|||||||
private static final boolean DBG = false;
|
private static final boolean DBG = false;
|
||||||
|
|
||||||
public static final String PERMISSION = android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD;
|
public static final String PERMISSION = android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD;
|
||||||
|
private static final int[] ADDRESS_FAMILIES = new int[] {AF_INET6, AF_INET};
|
||||||
|
|
||||||
/** Keeps track of keepalive requests. */
|
/** Keeps track of keepalive requests. */
|
||||||
private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives =
|
private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives =
|
||||||
@@ -107,17 +131,35 @@ public class KeepaliveTracker {
|
|||||||
// Allowed unprivileged keepalive slots per uid. Caller's permission will be enforced if
|
// Allowed unprivileged keepalive slots per uid. Caller's permission will be enforced if
|
||||||
// the number of remaining keepalive slots is less than or equal to the threshold.
|
// the number of remaining keepalive slots is less than or equal to the threshold.
|
||||||
private final int mAllowedUnprivilegedSlotsForUid;
|
private final int mAllowedUnprivilegedSlotsForUid;
|
||||||
|
/**
|
||||||
|
* The {@code inetDiagReqV2} messages for different IP family.
|
||||||
|
*
|
||||||
|
* Key: Ip family type.
|
||||||
|
* Value: Bytes array represent the {@code inetDiagReqV2}.
|
||||||
|
*
|
||||||
|
* This should only be accessed in the connectivity service handler thread.
|
||||||
|
*/
|
||||||
|
private final SparseArray<byte[]> mSockDiagMsg = new SparseArray<>();
|
||||||
|
private final Dependencies mDependencies;
|
||||||
|
private final INetd mNetd;
|
||||||
|
|
||||||
public KeepaliveTracker(Context context, Handler handler) {
|
public KeepaliveTracker(Context context, Handler handler) {
|
||||||
|
this(context, handler, new Dependencies(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public KeepaliveTracker(Context context, Handler handler, Dependencies dependencies) {
|
||||||
mConnectivityServiceHandler = handler;
|
mConnectivityServiceHandler = handler;
|
||||||
mTcpController = new TcpKeepaliveController(handler);
|
mTcpController = new TcpKeepaliveController(handler);
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mSupportedKeepalives = KeepaliveUtils.getSupportedKeepalives(mContext);
|
mDependencies = dependencies;
|
||||||
|
mSupportedKeepalives = mDependencies.getSupportedKeepalives();
|
||||||
|
mNetd = mDependencies.getNetd();
|
||||||
|
|
||||||
final ConnectivityResources res = new ConnectivityResources(mContext);
|
final Resources res = mDependencies.newConnectivityResources();
|
||||||
mReservedPrivilegedSlots = res.get().getInteger(
|
mReservedPrivilegedSlots = res.getInteger(
|
||||||
R.integer.config_reservedPrivilegedKeepaliveSlots);
|
R.integer.config_reservedPrivilegedKeepaliveSlots);
|
||||||
mAllowedUnprivilegedSlotsForUid = res.get().getInteger(
|
mAllowedUnprivilegedSlotsForUid = res.getInteger(
|
||||||
R.integer.config_allowedUnprivilegedKeepalivePerUid);
|
R.integer.config_allowedUnprivilegedKeepalivePerUid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -739,6 +781,9 @@ public class KeepaliveTracker {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump KeepaliveTracker state.
|
||||||
|
*/
|
||||||
public void dump(IndentingPrintWriter pw) {
|
public void dump(IndentingPrintWriter pw) {
|
||||||
pw.println("Supported Socket keepalives: " + Arrays.toString(mSupportedKeepalives));
|
pw.println("Supported Socket keepalives: " + Arrays.toString(mSupportedKeepalives));
|
||||||
pw.println("Reserved Privileged keepalives: " + mReservedPrivilegedSlots);
|
pw.println("Reserved Privileged keepalives: " + mReservedPrivilegedSlots);
|
||||||
@@ -756,4 +801,196 @@ public class KeepaliveTracker {
|
|||||||
}
|
}
|
||||||
pw.decreaseIndent();
|
pw.decreaseIndent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies class for testing.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
public static class Dependencies {
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
public Dependencies(final Context context) {
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a netlink socket connected to the kernel.
|
||||||
|
*
|
||||||
|
* @return fd the fileDescriptor of the socket.
|
||||||
|
*/
|
||||||
|
public FileDescriptor createConnectedNetlinkSocket()
|
||||||
|
throws ErrnoException, SocketException {
|
||||||
|
final FileDescriptor fd = NetlinkUtils.createNetLinkInetDiagSocket();
|
||||||
|
NetlinkUtils.connectSocketToNetlink(fd);
|
||||||
|
Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO,
|
||||||
|
StructTimeval.fromMillis(IO_TIMEOUT_MS));
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send composed message request to kernel.
|
||||||
|
*
|
||||||
|
* The given FileDescriptor is expected to be created by
|
||||||
|
* {@link #createConnectedNetlinkSocket} or equivalent way.
|
||||||
|
*
|
||||||
|
* @param fd a netlink socket {@code FileDescriptor} connected to the kernel.
|
||||||
|
* @param msg the byte array representing the request message to write to kernel.
|
||||||
|
*/
|
||||||
|
public void sendRequest(@NonNull final FileDescriptor fd,
|
||||||
|
@NonNull final byte[] msg)
|
||||||
|
throws ErrnoException, InterruptedIOException {
|
||||||
|
Os.write(fd, msg, 0 /* byteOffset */, msg.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an INetd connector.
|
||||||
|
*/
|
||||||
|
public INetd getNetd() {
|
||||||
|
return INetd.Stub.asInterface(
|
||||||
|
(IBinder) mContext.getSystemService(Context.NETD_SERVICE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive the response message from kernel via given {@code FileDescriptor}.
|
||||||
|
* The usage should follow the {@code #sendRequest} call with the same
|
||||||
|
* FileDescriptor.
|
||||||
|
*
|
||||||
|
* The overall response may be large but the individual messages should not be
|
||||||
|
* excessively large(8-16kB) because trying to get the kernel to return
|
||||||
|
* everything in one big buffer is inefficient as it forces the kernel to allocate
|
||||||
|
* large chunks of linearly physically contiguous memory. The usage should iterate the
|
||||||
|
* call of this method until the end of the overall message.
|
||||||
|
*
|
||||||
|
* The default receiving buffer size should be small enough that it is always
|
||||||
|
* processed within the {@link NetlinkUtils#IO_TIMEOUT_MS} timeout.
|
||||||
|
*/
|
||||||
|
public ByteBuffer recvSockDiagResponse(@NonNull final FileDescriptor fd)
|
||||||
|
throws ErrnoException, InterruptedIOException {
|
||||||
|
return NetlinkUtils.recvMessage(
|
||||||
|
fd, NetlinkUtils.DEFAULT_RECV_BUFSIZE, NetlinkUtils.IO_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read supported keepalive count for each transport type from overlay resource.
|
||||||
|
*/
|
||||||
|
public int[] getSupportedKeepalives() {
|
||||||
|
return KeepaliveUtils.getSupportedKeepalives(mContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new Resource from a new ConnectivityResources.
|
||||||
|
*/
|
||||||
|
public Resources newConnectivityResources() {
|
||||||
|
final ConnectivityResources resources = new ConnectivityResources(mContext);
|
||||||
|
return resources.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureRunningOnHandlerThread() {
|
||||||
|
if (mConnectivityServiceHandler.getLooper().getThread() != Thread.currentThread()) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Not running on handler thread: " + Thread.currentThread().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
boolean isAnyTcpSocketConnected(int netId) {
|
||||||
|
FileDescriptor fd = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fd = mDependencies.createConnectedNetlinkSocket();
|
||||||
|
|
||||||
|
// Get network mask
|
||||||
|
final MarkMaskParcel parcel = mNetd.getFwmarkForNetwork(netId);
|
||||||
|
final int networkMark = (parcel != null) ? parcel.mark : NetlinkUtils.UNKNOWN_MARK;
|
||||||
|
final int networkMask = (parcel != null) ? parcel.mask : NetlinkUtils.NULL_MASK;
|
||||||
|
|
||||||
|
// Send request for each IP family
|
||||||
|
for (final int family : ADDRESS_FAMILIES) {
|
||||||
|
if (isAnyTcpSocketConnectedForFamily(fd, family, networkMark, networkMask)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ErrnoException | SocketException | InterruptedIOException | RemoteException e) {
|
||||||
|
Log.e(TAG, "Fail to get socket info via netlink.", e);
|
||||||
|
} finally {
|
||||||
|
SocketUtils.closeSocketQuietly(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAnyTcpSocketConnectedForFamily(FileDescriptor fd, int family, int networkMark,
|
||||||
|
int networkMask) throws ErrnoException, InterruptedIOException {
|
||||||
|
ensureRunningOnHandlerThread();
|
||||||
|
// Build SocketDiag messages and cache it.
|
||||||
|
if (mSockDiagMsg.get(family) == null) {
|
||||||
|
mSockDiagMsg.put(family, InetDiagMessage.buildInetDiagReqForAliveTcpSockets(family));
|
||||||
|
}
|
||||||
|
mDependencies.sendRequest(fd, mSockDiagMsg.get(family));
|
||||||
|
|
||||||
|
// Iteration limitation as a protection to avoid possible infinite loops.
|
||||||
|
// DEFAULT_RECV_BUFSIZE could read more than 20 sockets per time. Max iteration
|
||||||
|
// should be enough to go through reasonable TCP sockets in the device.
|
||||||
|
final int maxIteration = 100;
|
||||||
|
int parsingIteration = 0;
|
||||||
|
while (parsingIteration < maxIteration) {
|
||||||
|
final ByteBuffer bytes = mDependencies.recvSockDiagResponse(fd);
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes)) {
|
||||||
|
final int startPos = bytes.position();
|
||||||
|
|
||||||
|
final int nlmsgLen = bytes.getInt();
|
||||||
|
final int nlmsgType = bytes.getShort();
|
||||||
|
if (isEndOfMessageOrError(nlmsgType)) return false;
|
||||||
|
// TODO: Parse InetDiagMessage to get uid and dst address information to filter
|
||||||
|
// socket via NetlinkMessage.parse.
|
||||||
|
|
||||||
|
// Skip the header to move to data part.
|
||||||
|
bytes.position(startPos + SOCKDIAG_MSG_HEADER_SIZE);
|
||||||
|
|
||||||
|
if (isTargetTcpSocket(bytes, nlmsgLen, networkMark, networkMask)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (BufferUnderflowException e) {
|
||||||
|
// The exception happens in random place in either header position or any data
|
||||||
|
// position. Partial bytes from the middle of the byte buffer may not be enough to
|
||||||
|
// clarify, so print out the content before the error to possibly prevent printing
|
||||||
|
// the whole 8K buffer.
|
||||||
|
final int exceptionPos = bytes.position();
|
||||||
|
final String hex = HexDump.dumpHexString(bytes.array(), 0, exceptionPos);
|
||||||
|
Log.e(TAG, "Unexpected socket info parsing: " + hex, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsingIteration++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEndOfMessageOrError(int nlmsgType) {
|
||||||
|
return nlmsgType == NLMSG_DONE || nlmsgType != SOCK_DIAG_BY_FAMILY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTargetTcpSocket(@NonNull ByteBuffer bytes, int nlmsgLen, int networkMark,
|
||||||
|
int networkMask) {
|
||||||
|
final int mark = readSocketDataAndReturnMark(bytes, nlmsgLen);
|
||||||
|
return (mark & networkMask) == networkMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readSocketDataAndReturnMark(@NonNull ByteBuffer bytes, int nlmsgLen) {
|
||||||
|
final int nextMsgOffset = bytes.position() + nlmsgLen - SOCKDIAG_MSG_HEADER_SIZE;
|
||||||
|
int mark = NetlinkUtils.INIT_MARK_VALUE;
|
||||||
|
// Get socket mark
|
||||||
|
// TODO: Add a parsing method in NetlinkMessage.parse to support this to skip the remaining
|
||||||
|
// data.
|
||||||
|
while (bytes.position() < nextMsgOffset) {
|
||||||
|
final StructNlAttr nlattr = StructNlAttr.parse(bytes);
|
||||||
|
if (nlattr != null && nlattr.nla_type == NetlinkUtils.INET_DIAG_MARK) {
|
||||||
|
mark = nlattr.getValueAsInteger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mark;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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 com.android.server.connectivity;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doNothing;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.net.INetd;
|
||||||
|
import android.net.MarkMaskParcel;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
|
import com.android.connectivity.resources.R;
|
||||||
|
import com.android.testutils.DevSdkIgnoreRule;
|
||||||
|
import com.android.testutils.DevSdkIgnoreRunner;
|
||||||
|
|
||||||
|
import libcore.util.HexEncoding;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
@RunWith(DevSdkIgnoreRunner.class)
|
||||||
|
@SmallTest
|
||||||
|
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
|
||||||
|
public class KeepaliveTrackerTest {
|
||||||
|
private static final int[] TEST_SUPPORTED_KEEPALIVES = {1, 3, 0, 0, 0, 0, 0, 0, 0};
|
||||||
|
private static final int TEST_NETID = 0xA85;
|
||||||
|
private static final int TEST_NETID_FWMARK = 0x0A85;
|
||||||
|
private static final int OTHER_NETID = 0x1A85;
|
||||||
|
private static final int NETID_MASK = 0xffff;
|
||||||
|
private static final int SUPPORTED_SLOT_COUNT = 2;
|
||||||
|
private KeepaliveTracker mKeepaliveTracker;
|
||||||
|
private HandlerThread mHandlerThread;
|
||||||
|
|
||||||
|
@Mock INetd mNetd;
|
||||||
|
@Mock KeepaliveTracker.Dependencies mDependencies;
|
||||||
|
@Mock Context mCtx;
|
||||||
|
@Mock Resources mResources;
|
||||||
|
|
||||||
|
// Hexadecimal representation of a SOCK_DIAG response with tcp info.
|
||||||
|
private static final String SOCK_DIAG_TCP_INET_HEX =
|
||||||
|
// struct nlmsghdr.
|
||||||
|
"14010000" + // length = 276
|
||||||
|
"1400" + // type = SOCK_DIAG_BY_FAMILY
|
||||||
|
"0301" + // flags = NLM_F_REQUEST | NLM_F_DUMP
|
||||||
|
"00000000" + // seqno
|
||||||
|
"00000000" + // pid (0 == kernel)
|
||||||
|
// struct inet_diag_req_v2
|
||||||
|
"02" + // family = AF_INET
|
||||||
|
"06" + // state
|
||||||
|
"00" + // timer
|
||||||
|
"00" + // retrans
|
||||||
|
// inet_diag_sockid
|
||||||
|
"DEA5" + // idiag_sport = 42462
|
||||||
|
"71B9" + // idiag_dport = 47473
|
||||||
|
"0a006402000000000000000000000000" + // idiag_src = 10.0.100.2
|
||||||
|
"08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
|
||||||
|
"00000000" + // idiag_if
|
||||||
|
"34ED000076270000" + // idiag_cookie = 43387759684916
|
||||||
|
"00000000" + // idiag_expires
|
||||||
|
"00000000" + // idiag_rqueue
|
||||||
|
"00000000" + // idiag_wqueue
|
||||||
|
"00000000" + // idiag_uid
|
||||||
|
"00000000" + // idiag_inode
|
||||||
|
// rtattr
|
||||||
|
"0500" + // len = 5
|
||||||
|
"0800" + // type = 8
|
||||||
|
"00000000" + // data
|
||||||
|
"0800" + // len = 8
|
||||||
|
"0F00" + // type = 15(INET_DIAG_MARK)
|
||||||
|
"850A0C00" + // data, socket mark=789125
|
||||||
|
"AC00" + // len = 172
|
||||||
|
"0200" + // type = 2(INET_DIAG_INFO)
|
||||||
|
// tcp_info
|
||||||
|
"01" + // state = TCP_ESTABLISHED
|
||||||
|
"00" + // ca_state = TCP_CA_OPEN
|
||||||
|
"05" + // retransmits = 5
|
||||||
|
"00" + // probes = 0
|
||||||
|
"00" + // backoff = 0
|
||||||
|
"07" + // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS
|
||||||
|
"88" + // wscale = 8
|
||||||
|
"00" + // delivery_rate_app_limited = 0
|
||||||
|
"4A911B00" + // rto = 1806666
|
||||||
|
"00000000" + // ato = 0
|
||||||
|
"2E050000" + // sndMss = 1326
|
||||||
|
"18020000" + // rcvMss = 536
|
||||||
|
"00000000" + // unsacked = 0
|
||||||
|
"00000000" + // acked = 0
|
||||||
|
"00000000" + // lost = 0
|
||||||
|
"00000000" + // retrans = 0
|
||||||
|
"00000000" + // fackets = 0
|
||||||
|
"BB000000" + // lastDataSent = 187
|
||||||
|
"00000000" + // lastAckSent = 0
|
||||||
|
"BB000000" + // lastDataRecv = 187
|
||||||
|
"BB000000" + // lastDataAckRecv = 187
|
||||||
|
"DC050000" + // pmtu = 1500
|
||||||
|
"30560100" + // rcvSsthresh = 87600
|
||||||
|
"3E2C0900" + // rttt = 601150
|
||||||
|
"1F960400" + // rttvar = 300575
|
||||||
|
"78050000" + // sndSsthresh = 1400
|
||||||
|
"0A000000" + // sndCwnd = 10
|
||||||
|
"A8050000" + // advmss = 1448
|
||||||
|
"03000000" + // reordering = 3
|
||||||
|
"00000000" + // rcvrtt = 0
|
||||||
|
"30560100" + // rcvspace = 87600
|
||||||
|
"00000000" + // totalRetrans = 0
|
||||||
|
"53AC000000000000" + // pacingRate = 44115
|
||||||
|
"FFFFFFFFFFFFFFFF" + // maxPacingRate = 18446744073709551615
|
||||||
|
"0100000000000000" + // bytesAcked = 1
|
||||||
|
"0000000000000000" + // bytesReceived = 0
|
||||||
|
"0A000000" + // SegsOut = 10
|
||||||
|
"00000000" + // SegsIn = 0
|
||||||
|
"00000000" + // NotSentBytes = 0
|
||||||
|
"3E2C0900" + // minRtt = 601150
|
||||||
|
"00000000" + // DataSegsIn = 0
|
||||||
|
"00000000" + // DataSegsOut = 0
|
||||||
|
"0000000000000000"; // deliverRate = 0
|
||||||
|
private static final String SOCK_DIAG_NO_TCP_INET_HEX =
|
||||||
|
// struct nlmsghdr
|
||||||
|
"14000000" // length = 20
|
||||||
|
+ "0300" // type = NLMSG_DONE
|
||||||
|
+ "0301" // flags = NLM_F_REQUEST | NLM_F_DUMP
|
||||||
|
+ "00000000" // seqno
|
||||||
|
+ "00000000" // pid (0 == kernel)
|
||||||
|
// struct inet_diag_req_v2
|
||||||
|
+ "02" // family = AF_INET
|
||||||
|
+ "06" // state
|
||||||
|
+ "00" // timer
|
||||||
|
+ "00"; // retrans
|
||||||
|
private static final byte[] SOCK_DIAG_NO_TCP_INET_BYTES =
|
||||||
|
HexEncoding.decode(SOCK_DIAG_NO_TCP_INET_HEX.toCharArray(), false);
|
||||||
|
private static final String TEST_RESPONSE_HEX =
|
||||||
|
SOCK_DIAG_TCP_INET_HEX + SOCK_DIAG_NO_TCP_INET_HEX;
|
||||||
|
private static final byte[] TEST_RESPONSE_BYTES =
|
||||||
|
HexEncoding.decode(TEST_RESPONSE_HEX.toCharArray(), false);
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws Exception {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
|
doReturn(mNetd).when(mDependencies).getNetd();
|
||||||
|
doReturn(makeMarkMaskParcel(NETID_MASK, TEST_NETID_FWMARK)).when(mNetd)
|
||||||
|
.getFwmarkForNetwork(TEST_NETID);
|
||||||
|
|
||||||
|
doReturn(TEST_SUPPORTED_KEEPALIVES).when(mDependencies).getSupportedKeepalives();
|
||||||
|
doReturn(mResources).when(mDependencies).newConnectivityResources();
|
||||||
|
mockResource();
|
||||||
|
doNothing().when(mDependencies).sendRequest(any(), any());
|
||||||
|
|
||||||
|
mHandlerThread = new HandlerThread("KeepaliveTrackerTest");
|
||||||
|
mHandlerThread.start();
|
||||||
|
|
||||||
|
mKeepaliveTracker = new KeepaliveTracker(mCtx, mHandlerThread.getThreadHandler(),
|
||||||
|
mDependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mockResource() {
|
||||||
|
doReturn(SUPPORTED_SLOT_COUNT).when(mResources).getInteger(
|
||||||
|
R.integer.config_reservedPrivilegedKeepaliveSlots);
|
||||||
|
doReturn(SUPPORTED_SLOT_COUNT).when(mResources).getInteger(
|
||||||
|
R.integer.config_allowedUnprivilegedKeepalivePerUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsAnyTcpSocketConnected_runOnNonHandlerThread() throws Exception {
|
||||||
|
setupResponseWithSocketExisting();
|
||||||
|
assertThrows(IllegalStateException.class,
|
||||||
|
() -> mKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsAnyTcpSocketConnected_withTargetNetId() throws Exception {
|
||||||
|
setupResponseWithSocketExisting();
|
||||||
|
mHandlerThread.getThreadHandler().post(
|
||||||
|
() -> assertTrue(mKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsAnyTcpSocketConnected_withIncorrectNetId() throws Exception {
|
||||||
|
setupResponseWithSocketExisting();
|
||||||
|
mHandlerThread.getThreadHandler().post(
|
||||||
|
() -> assertFalse(mKeepaliveTracker.isAnyTcpSocketConnected(OTHER_NETID)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsAnyTcpSocketConnected_noSocketExists() throws Exception {
|
||||||
|
setupResponseWithoutSocketExisting();
|
||||||
|
mHandlerThread.getThreadHandler().post(
|
||||||
|
() -> assertFalse(mKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupResponseWithSocketExisting() throws Exception {
|
||||||
|
final ByteBuffer tcpBufferV6 = getByteBuffer(TEST_RESPONSE_BYTES);
|
||||||
|
final ByteBuffer tcpBufferV4 = getByteBuffer(TEST_RESPONSE_BYTES);
|
||||||
|
doReturn(tcpBufferV6, tcpBufferV4).when(mDependencies).recvSockDiagResponse(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupResponseWithoutSocketExisting() throws Exception {
|
||||||
|
final ByteBuffer tcpBufferV6 = getByteBuffer(SOCK_DIAG_NO_TCP_INET_BYTES);
|
||||||
|
final ByteBuffer tcpBufferV4 = getByteBuffer(SOCK_DIAG_NO_TCP_INET_BYTES);
|
||||||
|
doReturn(tcpBufferV6, tcpBufferV4).when(mDependencies).recvSockDiagResponse(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
private MarkMaskParcel makeMarkMaskParcel(final int mask, final int mark) {
|
||||||
|
final MarkMaskParcel parcel = new MarkMaskParcel();
|
||||||
|
parcel.mask = mask;
|
||||||
|
parcel.mark = mark;
|
||||||
|
return parcel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ByteBuffer getByteBuffer(final byte[] bytes) {
|
||||||
|
final ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
||||||
|
buffer.order(ByteOrder.nativeOrder());
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user