Merge "Add common netlink operation and definition for TCP socket polling"

This commit is contained in:
Chiachang Wang 2022-12-19 12:40:41 +00:00 • committed by Gerrit Code Review
commit da152d63b4
7 changed files with 216 additions and 212 deletions

View file

@ -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;

View file

@ -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);

View file

@ -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{ "

View file

@ -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);
}
}

View file

@ -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() {}
}

View file

@ -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);

View file

@ -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;
}
}