Add common netlink operation and definition for TCP socket polling
Move code to common lib to support coming TCP socket polling design in connectivity module to prevent code duplication. Bug: 259000745 Test: atest FrameworksNetTests Change-Id: I89ca1c19122c3b4306b93a6a253e0ea67259eb77
This commit is contained in:
parent
f618ab0769
commit
29bbf2dace
7 changed files with 216 additions and 212 deletions
|
|
@ -31,7 +31,7 @@ import android.util.Log;
|
|||
|
||||
import com.android.net.module.util.SharedLog;
|
||||
import com.android.net.module.util.netlink.NetlinkMessage;
|
||||
import com.android.net.module.util.netlink.NetlinkSocket;
|
||||
import com.android.net.module.util.netlink.NetlinkUtils;
|
||||
import com.android.net.module.util.netlink.RtNetlinkNeighborMessage;
|
||||
import com.android.net.module.util.netlink.StructNdMsg;
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ public class IpNeighborMonitor extends NetlinkMonitor {
|
|||
1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
|
||||
|
||||
try {
|
||||
NetlinkSocket.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
|
||||
NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
|
||||
} catch (ErrnoException e) {
|
||||
Log.e(TAG, "Error " + msgSnippet + ": " + e);
|
||||
return -e.errno;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import com.android.net.module.util.PacketReader;
|
|||
import com.android.net.module.util.SharedLog;
|
||||
import com.android.net.module.util.netlink.NetlinkErrorMessage;
|
||||
import com.android.net.module.util.netlink.NetlinkMessage;
|
||||
import com.android.net.module.util.netlink.NetlinkSocket;
|
||||
import com.android.net.module.util.netlink.NetlinkUtils;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.net.SocketAddress;
|
||||
|
|
@ -82,7 +82,7 @@ public class NetlinkMonitor extends PacketReader {
|
|||
*/
|
||||
public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
|
||||
int family, int bindGroups, int sockRcvbufSize) {
|
||||
super(h, NetlinkSocket.DEFAULT_RECV_BUFSIZE);
|
||||
super(h, NetlinkUtils.DEFAULT_RECV_BUFSIZE);
|
||||
mLog = log.forSubComponent(tag);
|
||||
mTag = tag;
|
||||
mFamily = family;
|
||||
|
|
@ -109,7 +109,7 @@ public class NetlinkMonitor extends PacketReader {
|
|||
}
|
||||
}
|
||||
Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));
|
||||
NetlinkSocket.connectToKernel(fd);
|
||||
NetlinkUtils.connectSocketToNetlink(fd);
|
||||
|
||||
if (DBG) {
|
||||
final SocketAddress nlAddr = Os.getsockname(fd);
|
||||
|
|
|
|||
|
|
@ -19,11 +19,12 @@ package com.android.net.module.util.netlink;
|
|||
import static android.os.Process.INVALID_UID;
|
||||
import static android.system.OsConstants.AF_INET;
|
||||
import static android.system.OsConstants.AF_INET6;
|
||||
import static android.system.OsConstants.IPPROTO_TCP;
|
||||
import static android.system.OsConstants.IPPROTO_UDP;
|
||||
import static android.system.OsConstants.NETLINK_INET_DIAG;
|
||||
|
||||
import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
|
||||
import static com.android.net.module.util.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
|
||||
import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE;
|
||||
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
|
||||
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
|
||||
|
||||
|
|
@ -132,8 +133,8 @@ public class InetDiagMessage extends NetlinkMessage {
|
|||
FileDescriptor fd)
|
||||
throws ErrnoException, InterruptedIOException {
|
||||
byte[] msg = inetDiagReqV2(protocol, local, remote, family, flags);
|
||||
NetlinkSocket.sendMessage(fd, msg, 0, msg.length, TIMEOUT_MS);
|
||||
ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT_MS);
|
||||
NetlinkUtils.sendMessage(fd, msg, 0, msg.length, TIMEOUT_MS);
|
||||
ByteBuffer response = NetlinkUtils.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT_MS);
|
||||
|
||||
final NetlinkMessage nlMsg = NetlinkMessage.parse(response, NETLINK_INET_DIAG);
|
||||
if (nlMsg == null) {
|
||||
|
|
@ -210,8 +211,8 @@ public class InetDiagMessage extends NetlinkMessage {
|
|||
int uid = INVALID_UID;
|
||||
FileDescriptor fd = null;
|
||||
try {
|
||||
fd = NetlinkSocket.forProto(NETLINK_INET_DIAG);
|
||||
NetlinkSocket.connectToKernel(fd);
|
||||
fd = NetlinkUtils.netlinkSocketForProto(NETLINK_INET_DIAG);
|
||||
NetlinkUtils.connectSocketToNetlink(fd);
|
||||
uid = lookupUid(protocol, local, remote, fd);
|
||||
} catch (ErrnoException | SocketException | IllegalArgumentException
|
||||
| InterruptedIOException e) {
|
||||
|
|
@ -228,6 +229,20 @@ public class InetDiagMessage extends NetlinkMessage {
|
|||
return uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an inet_diag_req_v2 message for querying alive TCP sockets from kernel.
|
||||
*/
|
||||
public static byte[] buildInetDiagReqForAliveTcpSockets(int family) {
|
||||
return inetDiagReqV2(IPPROTO_TCP,
|
||||
null /* local addr */,
|
||||
null /* remote addr */,
|
||||
family,
|
||||
(short) (StructNlMsgHdr.NLM_F_REQUEST | StructNlMsgHdr.NLM_F_DUMP) /* flag */,
|
||||
0 /* pad */,
|
||||
1 << NetlinkConstants.INET_DIAG_MEMINFO /* idiagExt */,
|
||||
NetlinkUtils.TCP_MONITOR_STATE_FILTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InetDiagMessage{ "
|
||||
|
|
|
|||
|
|
@ -1,174 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.net.module.util.netlink;
|
||||
|
||||
import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
|
||||
import static android.system.OsConstants.AF_NETLINK;
|
||||
import static android.system.OsConstants.EIO;
|
||||
import static android.system.OsConstants.EPROTO;
|
||||
import static android.system.OsConstants.ETIMEDOUT;
|
||||
import static android.system.OsConstants.SOCK_DGRAM;
|
||||
import static android.system.OsConstants.SOL_SOCKET;
|
||||
import static android.system.OsConstants.SO_RCVBUF;
|
||||
import static android.system.OsConstants.SO_RCVTIMEO;
|
||||
import static android.system.OsConstants.SO_SNDTIMEO;
|
||||
|
||||
import android.net.util.SocketUtils;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.StructTimeval;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
|
||||
/**
|
||||
* NetlinkSocket
|
||||
*
|
||||
* A small static class to assist with AF_NETLINK socket operations.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class NetlinkSocket {
|
||||
private static final String TAG = "NetlinkSocket";
|
||||
private static final long IO_TIMEOUT_MS = 300L;
|
||||
|
||||
public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
|
||||
public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
|
||||
|
||||
/**
|
||||
* Send one netlink message to kernel via netlink socket.
|
||||
*
|
||||
* @param nlProto netlink protocol type.
|
||||
* @param msg the raw bytes of netlink message to be sent.
|
||||
*/
|
||||
public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
|
||||
final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
|
||||
final FileDescriptor fd = forProto(nlProto);
|
||||
|
||||
try {
|
||||
connectToKernel(fd);
|
||||
sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT_MS);
|
||||
final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS);
|
||||
// recvMessage() guaranteed to not return null if it did not throw.
|
||||
final NetlinkMessage response = NetlinkMessage.parse(bytes, nlProto);
|
||||
if (response != null && response instanceof NetlinkErrorMessage
|
||||
&& (((NetlinkErrorMessage) response).getNlMsgError() != null)) {
|
||||
final int errno = ((NetlinkErrorMessage) response).getNlMsgError().error;
|
||||
if (errno != 0) {
|
||||
// TODO: consider ignoring EINVAL (-22), which appears to be
|
||||
// normal when probing a neighbor for which the kernel does
|
||||
// not already have / no longer has a link layer address.
|
||||
Log.e(TAG, errPrefix + ", errmsg=" + response.toString());
|
||||
// Note: convert kernel errnos (negative) into userspace errnos (positive).
|
||||
throw new ErrnoException(response.toString(), Math.abs(errno));
|
||||
}
|
||||
} else {
|
||||
final String errmsg;
|
||||
if (response == null) {
|
||||
bytes.position(0);
|
||||
errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
|
||||
} else {
|
||||
errmsg = response.toString();
|
||||
}
|
||||
Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
|
||||
throw new ErrnoException(errmsg, EPROTO);
|
||||
}
|
||||
} catch (InterruptedIOException e) {
|
||||
Log.e(TAG, errPrefix, e);
|
||||
throw new ErrnoException(errPrefix, ETIMEDOUT, e);
|
||||
} catch (SocketException e) {
|
||||
Log.e(TAG, errPrefix, e);
|
||||
throw new ErrnoException(errPrefix, EIO, e);
|
||||
} finally {
|
||||
try {
|
||||
SocketUtils.closeSocket(fd);
|
||||
} catch (IOException e) {
|
||||
// Nothing we can do here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create netlink socket with the given netlink protocol type.
|
||||
*
|
||||
* @return fd the fileDescriptor of the socket.
|
||||
* Throw ErrnoException if the exception is thrown.
|
||||
*/
|
||||
public static FileDescriptor forProto(int nlProto) throws ErrnoException {
|
||||
final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
|
||||
Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
|
||||
return fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to kernel via netlink socket.
|
||||
*
|
||||
* Throw ErrnoException, SocketException if the exception is thrown.
|
||||
*/
|
||||
public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException {
|
||||
Os.connect(fd, makeNetlinkSocketAddress(0, 0));
|
||||
}
|
||||
|
||||
private static void checkTimeout(long timeoutMs) {
|
||||
if (timeoutMs < 0) {
|
||||
throw new IllegalArgumentException("Negative timeouts not permitted");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait up to |timeoutMs| (or until underlying socket error) for a
|
||||
* netlink message of at most |bufsize| size.
|
||||
*
|
||||
* Multi-threaded calls with different timeouts will cause unexpected results.
|
||||
*/
|
||||
public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)
|
||||
throws ErrnoException, IllegalArgumentException, InterruptedIOException {
|
||||
checkTimeout(timeoutMs);
|
||||
|
||||
Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
|
||||
int length = Os.read(fd, byteBuffer);
|
||||
if (length == bufsize) {
|
||||
Log.w(TAG, "maximum read");
|
||||
}
|
||||
byteBuffer.position(0);
|
||||
byteBuffer.limit(length);
|
||||
byteBuffer.order(ByteOrder.nativeOrder());
|
||||
return byteBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to a peer to which this socket has previously connected,
|
||||
* waiting at most |timeoutMs| milliseconds for the send to complete.
|
||||
*
|
||||
* Multi-threaded calls with different timeouts will cause unexpected results.
|
||||
*/
|
||||
public static int sendMessage(
|
||||
FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)
|
||||
throws ErrnoException, IllegalArgumentException, InterruptedIOException {
|
||||
checkTimeout(timeoutMs);
|
||||
Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
|
||||
return Os.write(fd, bytes, offset, count);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,13 +16,40 @@
|
|||
|
||||
package com.android.net.module.util.netlink;
|
||||
|
||||
import static android.system.OsConstants.IPPROTO_TCP;
|
||||
import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
|
||||
import static android.system.OsConstants.AF_NETLINK;
|
||||
import static android.system.OsConstants.EIO;
|
||||
import static android.system.OsConstants.EPROTO;
|
||||
import static android.system.OsConstants.ETIMEDOUT;
|
||||
import static android.system.OsConstants.NETLINK_INET_DIAG;
|
||||
import static android.system.OsConstants.SOCK_CLOEXEC;
|
||||
import static android.system.OsConstants.SOCK_DGRAM;
|
||||
import static android.system.OsConstants.SOL_SOCKET;
|
||||
import static android.system.OsConstants.SO_RCVBUF;
|
||||
import static android.system.OsConstants.SO_RCVTIMEO;
|
||||
import static android.system.OsConstants.SO_SNDTIMEO;
|
||||
|
||||
import android.net.util.SocketUtils;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.StructTimeval;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Utilities for netlink related class.
|
||||
* Utilities for netlink related class that may not be able to fit into a specific class.
|
||||
* @hide
|
||||
*/
|
||||
public class NetlinkUtils {
|
||||
private static final String TAG = "NetlinkUtils";
|
||||
/** Corresponds to enum from bionic/libc/include/netinet/tcp.h. */
|
||||
private static final int TCP_ESTABLISHED = 1;
|
||||
private static final int TCP_SYN_SENT = 2;
|
||||
|
|
@ -31,17 +58,155 @@ public class NetlinkUtils {
|
|||
public static final int TCP_MONITOR_STATE_FILTER =
|
||||
(1 << TCP_ESTABLISHED) | (1 << TCP_SYN_SENT) | (1 << TCP_SYN_RECV);
|
||||
|
||||
public static final int UNKNOWN_MARK = 0xffffffff;
|
||||
public static final int NULL_MASK = 0;
|
||||
|
||||
// Initial mark value corresponds to the initValue in system/netd/include/Fwmark.h.
|
||||
public static final int INIT_MARK_VALUE = 0;
|
||||
// Corresponds to enum definition in bionic/libc/kernel/uapi/linux/inet_diag.h
|
||||
public static final int INET_DIAG_INFO = 2;
|
||||
public static final int INET_DIAG_MARK = 15;
|
||||
|
||||
public static final long IO_TIMEOUT_MS = 300L;
|
||||
|
||||
public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
|
||||
public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
|
||||
|
||||
/**
|
||||
* Construct an inet_diag_req_v2 message for querying alive TCP sockets from kernel.
|
||||
* Return whether the input ByteBuffer contains enough remaining bytes for
|
||||
* {@code StructNlMsgHdr}.
|
||||
*/
|
||||
public static byte[] buildInetDiagReqForAliveTcpSockets(int family) {
|
||||
return InetDiagMessage.inetDiagReqV2(IPPROTO_TCP,
|
||||
null /* local addr */,
|
||||
null /* remote addr */,
|
||||
family,
|
||||
(short) (StructNlMsgHdr.NLM_F_REQUEST | StructNlMsgHdr.NLM_F_DUMP) /* flag */,
|
||||
0 /* pad */,
|
||||
1 << NetlinkConstants.INET_DIAG_MEMINFO /* idiagExt */,
|
||||
TCP_MONITOR_STATE_FILTER);
|
||||
public static boolean enoughBytesRemainForValidNlMsg(@NonNull final ByteBuffer bytes) {
|
||||
return bytes.remaining() >= StructNlMsgHdr.STRUCT_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send one netlink message to kernel via netlink socket.
|
||||
*
|
||||
* @param nlProto netlink protocol type.
|
||||
* @param msg the raw bytes of netlink message to be sent.
|
||||
*/
|
||||
public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
|
||||
final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
|
||||
final FileDescriptor fd = netlinkSocketForProto(nlProto);
|
||||
|
||||
try {
|
||||
connectSocketToNetlink(fd);
|
||||
sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT_MS);
|
||||
final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS);
|
||||
// recvMessage() guaranteed to not return null if it did not throw.
|
||||
final NetlinkMessage response = NetlinkMessage.parse(bytes, nlProto);
|
||||
if (response != null && response instanceof NetlinkErrorMessage
|
||||
&& (((NetlinkErrorMessage) response).getNlMsgError() != null)) {
|
||||
final int errno = ((NetlinkErrorMessage) response).getNlMsgError().error;
|
||||
if (errno != 0) {
|
||||
// TODO: consider ignoring EINVAL (-22), which appears to be
|
||||
// normal when probing a neighbor for which the kernel does
|
||||
// not already have / no longer has a link layer address.
|
||||
Log.e(TAG, errPrefix + ", errmsg=" + response.toString());
|
||||
// Note: convert kernel errnos (negative) into userspace errnos (positive).
|
||||
throw new ErrnoException(response.toString(), Math.abs(errno));
|
||||
}
|
||||
} else {
|
||||
final String errmsg;
|
||||
if (response == null) {
|
||||
bytes.position(0);
|
||||
errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
|
||||
} else {
|
||||
errmsg = response.toString();
|
||||
}
|
||||
Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
|
||||
throw new ErrnoException(errmsg, EPROTO);
|
||||
}
|
||||
} catch (InterruptedIOException e) {
|
||||
Log.e(TAG, errPrefix, e);
|
||||
throw new ErrnoException(errPrefix, ETIMEDOUT, e);
|
||||
} catch (SocketException e) {
|
||||
Log.e(TAG, errPrefix, e);
|
||||
throw new ErrnoException(errPrefix, EIO, e);
|
||||
} finally {
|
||||
try {
|
||||
SocketUtils.closeSocket(fd);
|
||||
} catch (IOException e) {
|
||||
// Nothing we can do here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create netlink socket with the given netlink protocol type.
|
||||
*
|
||||
* @return fd the fileDescriptor of the socket.
|
||||
* @throws ErrnoException if the FileDescriptor not connect to be created successfully
|
||||
*/
|
||||
public static FileDescriptor netlinkSocketForProto(int nlProto) throws ErrnoException {
|
||||
final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
|
||||
Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
|
||||
return fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a netlink inet_diag socket.
|
||||
*/
|
||||
public static FileDescriptor createNetLinkInetDiagSocket() throws ErrnoException {
|
||||
return Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_INET_DIAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect the given file descriptor to the Netlink interface to the kernel.
|
||||
*
|
||||
* The fd must be a SOCK_DGRAM socket : create it with {@link #netlinkSocketForProto}
|
||||
*
|
||||
* @throws ErrnoException if the {@code fd} could not connect to kernel successfully
|
||||
* @throws SocketException if there is an error accessing a socket.
|
||||
*/
|
||||
public static void connectSocketToNetlink(FileDescriptor fd)
|
||||
throws ErrnoException, SocketException {
|
||||
Os.connect(fd, makeNetlinkSocketAddress(0, 0));
|
||||
}
|
||||
|
||||
private static void checkTimeout(long timeoutMs) {
|
||||
if (timeoutMs < 0) {
|
||||
throw new IllegalArgumentException("Negative timeouts not permitted");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait up to |timeoutMs| (or until underlying socket error) for a
|
||||
* netlink message of at most |bufsize| size.
|
||||
*
|
||||
* Multi-threaded calls with different timeouts will cause unexpected results.
|
||||
*/
|
||||
public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)
|
||||
throws ErrnoException, IllegalArgumentException, InterruptedIOException {
|
||||
checkTimeout(timeoutMs);
|
||||
|
||||
Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
|
||||
|
||||
final ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
|
||||
final int length = Os.read(fd, byteBuffer);
|
||||
if (length == bufsize) {
|
||||
Log.w(TAG, "maximum read");
|
||||
}
|
||||
byteBuffer.position(0);
|
||||
byteBuffer.limit(length);
|
||||
byteBuffer.order(ByteOrder.nativeOrder());
|
||||
return byteBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to a peer to which this socket has previously connected.
|
||||
*
|
||||
* This waits at most |timeoutMs| milliseconds for the send to complete, will get the exception
|
||||
* if it times out.
|
||||
*/
|
||||
public static int sendMessage(
|
||||
FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)
|
||||
throws ErrnoException, IllegalArgumentException, InterruptedIOException {
|
||||
checkTimeout(timeoutMs);
|
||||
Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
|
||||
return Os.write(fd, bytes, offset, count);
|
||||
}
|
||||
|
||||
private NetlinkUtils() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ import androidx.test.runner.AndroidJUnit4;
|
|||
import com.android.net.module.util.SharedLog;
|
||||
import com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent;
|
||||
import com.android.net.module.util.netlink.NetlinkConstants;
|
||||
import com.android.net.module.util.netlink.NetlinkSocket;
|
||||
import com.android.net.module.util.netlink.NetlinkUtils;
|
||||
|
||||
import libcore.util.HexEncoding;
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ public class ConntrackMonitorTest {
|
|||
private void sendMessage(byte[] msg) {
|
||||
mHandler.post(() -> {
|
||||
try {
|
||||
NetlinkSocket.sendMessage(mWriteFd, msg, 0 /* offset */, msg.length,
|
||||
NetlinkUtils.sendMessage(mWriteFd, msg, 0 /* offset */, msg.length,
|
||||
TIMEOUT_MS);
|
||||
} catch (ErrnoException | InterruptedIOException e) {
|
||||
fail("Unable to send netfilter message: " + e);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.net.module.util.netlink;
|
||||
|
||||
import static android.system.OsConstants.AF_INET;
|
||||
import static android.system.OsConstants.AF_INET6;
|
||||
import static android.system.OsConstants.AF_UNSPEC;
|
||||
import static android.system.OsConstants.EACCES;
|
||||
import static android.system.OsConstants.NETLINK_ROUTE;
|
||||
|
||||
import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE;
|
||||
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
|
||||
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.content.Context;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.NetlinkSocketAddress;
|
||||
import android.system.Os;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.modules.utils.build.SdkLevel;
|
||||
import com.android.net.module.util.Struct;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class NetlinkUtilsTest {
|
||||
private static final String TAG = "NetlinkUitlsTest";
|
||||
private static final int TEST_SEQNO = 5;
|
||||
private static final int TEST_TIMEOUT_MS = 500;
|
||||
|
||||
@Test
|
||||
public void testGetNeighborsQuery() throws Exception {
|
||||
final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_ROUTE);
|
||||
assertNotNull(fd);
|
||||
|
||||
NetlinkUtils.connectSocketToNetlink(fd);
|
||||
|
||||
final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
|
||||
assertNotNull(localAddr);
|
||||
assertEquals(0, localAddr.getGroupsMask());
|
||||
assertTrue(0 != localAddr.getPortId());
|
||||
|
||||
final byte[] req = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO);
|
||||
assertNotNull(req);
|
||||
|
||||
final Context ctx = InstrumentationRegistry.getInstrumentation().getContext();
|
||||
final int targetSdk =
|
||||
ctx.getPackageManager()
|
||||
.getApplicationInfo(ctx.getPackageName(), 0)
|
||||
.targetSdkVersion;
|
||||
|
||||
// Apps targeting an SDK version > S are not allowed to send RTM_GETNEIGH{TBL} messages
|
||||
if (SdkLevel.isAtLeastT() && targetSdk > 31) {
|
||||
try {
|
||||
NetlinkUtils.sendMessage(fd, req, 0, req.length, TEST_TIMEOUT_MS);
|
||||
fail("RTM_GETNEIGH is not allowed for apps targeting SDK > 31 on T+ platforms,"
|
||||
+ " target SDK version: " + targetSdk);
|
||||
} catch (ErrnoException e) {
|
||||
// Expected
|
||||
assertEquals(e.errno, EACCES);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that apps targeting lower API levels / running on older platforms succeed
|
||||
assertEquals(req.length,
|
||||
NetlinkUtils.sendMessage(fd, req, 0, req.length, TEST_TIMEOUT_MS));
|
||||
|
||||
int neighMessageCount = 0;
|
||||
int doneMessageCount = 0;
|
||||
|
||||
while (doneMessageCount == 0) {
|
||||
ByteBuffer response =
|
||||
NetlinkUtils.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TEST_TIMEOUT_MS);
|
||||
assertNotNull(response);
|
||||
assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit());
|
||||
assertEquals(0, response.position());
|
||||
assertEquals(ByteOrder.nativeOrder(), response.order());
|
||||
|
||||
// Verify the messages at least appears minimally reasonable.
|
||||
while (response.remaining() > 0) {
|
||||
final NetlinkMessage msg = NetlinkMessage.parse(response, NETLINK_ROUTE);
|
||||
assertNotNull(msg);
|
||||
final StructNlMsgHdr hdr = msg.getHeader();
|
||||
assertNotNull(hdr);
|
||||
|
||||
if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
|
||||
doneMessageCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type);
|
||||
assertTrue(msg instanceof RtNetlinkNeighborMessage);
|
||||
assertTrue((hdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0);
|
||||
assertEquals(TEST_SEQNO, hdr.nlmsg_seq);
|
||||
assertEquals(localAddr.getPortId(), hdr.nlmsg_pid);
|
||||
|
||||
neighMessageCount++;
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(1, doneMessageCount);
|
||||
// TODO: make sure this test passes sanely in airplane mode.
|
||||
assertTrue(neighMessageCount > 0);
|
||||
|
||||
IoUtils.closeQuietly(fd);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicWorkingGetAddrQuery() throws Exception {
|
||||
final FileDescriptor fd = NetlinkUtils.netlinkSocketForProto(NETLINK_ROUTE);
|
||||
assertNotNull(fd);
|
||||
|
||||
NetlinkUtils.connectSocketToNetlink(fd);
|
||||
|
||||
final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
|
||||
assertNotNull(localAddr);
|
||||
assertEquals(0, localAddr.getGroupsMask());
|
||||
assertTrue(0 != localAddr.getPortId());
|
||||
|
||||
final int testSeqno = 8;
|
||||
final byte[] req = newGetAddrRequest(testSeqno);
|
||||
assertNotNull(req);
|
||||
|
||||
final long timeout = 500;
|
||||
assertEquals(req.length, NetlinkUtils.sendMessage(fd, req, 0, req.length, timeout));
|
||||
|
||||
int addrMessageCount = 0;
|
||||
|
||||
while (true) {
|
||||
ByteBuffer response = NetlinkUtils.recvMessage(fd, DEFAULT_RECV_BUFSIZE, timeout);
|
||||
assertNotNull(response);
|
||||
assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit());
|
||||
assertEquals(0, response.position());
|
||||
assertEquals(ByteOrder.nativeOrder(), response.order());
|
||||
|
||||
final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(response);
|
||||
assertNotNull(nlmsghdr);
|
||||
|
||||
if (nlmsghdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
|
||||
break;
|
||||
}
|
||||
|
||||
assertEquals(NetlinkConstants.RTM_NEWADDR, nlmsghdr.nlmsg_type);
|
||||
assertTrue((nlmsghdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0);
|
||||
assertEquals(testSeqno, nlmsghdr.nlmsg_seq);
|
||||
assertEquals(localAddr.getPortId(), nlmsghdr.nlmsg_pid);
|
||||
addrMessageCount++;
|
||||
|
||||
final IfaddrMsg ifaMsg = Struct.parse(IfaddrMsg.class, response);
|
||||
assertTrue(
|
||||
"Non-IP address family: " + ifaMsg.family,
|
||||
ifaMsg.family == AF_INET || ifaMsg.family == AF_INET6);
|
||||
}
|
||||
|
||||
assertTrue(addrMessageCount > 0);
|
||||
|
||||
IoUtils.closeQuietly(fd);
|
||||
}
|
||||
|
||||
/** A convenience method to create an RTM_GETADDR request message. */
|
||||
private static byte[] newGetAddrRequest(int seqNo) {
|
||||
final int length = StructNlMsgHdr.STRUCT_SIZE + Struct.getSize(RtgenMsg.class);
|
||||
final byte[] bytes = new byte[length];
|
||||
final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
|
||||
byteBuffer.order(ByteOrder.nativeOrder());
|
||||
|
||||
final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
|
||||
nlmsghdr.nlmsg_len = length;
|
||||
nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETADDR;
|
||||
nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
|
||||
nlmsghdr.nlmsg_seq = seqNo;
|
||||
nlmsghdr.pack(byteBuffer);
|
||||
|
||||
final RtgenMsg rtgenMsg = new RtgenMsg();
|
||||
rtgenMsg.family = (byte) AF_UNSPEC;
|
||||
rtgenMsg.writeToByteBuffer(byteBuffer);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/** From uapi/linux/rtnetlink.h */
|
||||
private static class RtgenMsg extends Struct {
|
||||
@Field(order = 0, type = Type.U8)
|
||||
public short family;
|
||||
}
|
||||
|
||||
/**
|
||||
* From uapi/linux/ifaddr.h
|
||||
*
|
||||
* Public ensures visibility to Struct class
|
||||
*/
|
||||
public static class IfaddrMsg extends Struct {
|
||||
@Field(order = 0, type = Type.U8)
|
||||
public short family;
|
||||
|
||||
@Field(order = 1, type = Type.U8)
|
||||
public short prefixlen;
|
||||
|
||||
@Field(order = 2, type = Type.U8)
|
||||
public short flags;
|
||||
|
||||
@Field(order = 3, type = Type.U8)
|
||||
public short scope;
|
||||
|
||||
@Field(order = 4, type = Type.S32)
|
||||
public int index;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue