| /* |
| * Copyright (C) 2018 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.netlink; |
| |
| import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP; |
| import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST; |
| 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.SOCK_DGRAM; |
| import static android.system.OsConstants.SOCK_STREAM; |
| |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import android.app.Instrumentation; |
| import android.content.Context; |
| import android.net.ConnectivityManager; |
| import android.net.netlink.StructNlMsgHdr; |
| import android.os.Process; |
| import android.system.Os; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.filters.SmallTest; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import libcore.util.HexEncoding; |
| |
| import org.junit.Before; |
| import org.junit.Ignore; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.io.FileDescriptor; |
| import java.net.Inet4Address; |
| import java.net.Inet6Address; |
| import java.net.InetAddress; |
| import java.net.InetSocketAddress; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| |
| @RunWith(AndroidJUnit4.class) |
| @SmallTest |
| public class InetDiagSocketTest { |
| private final String TAG = "InetDiagSocketTest"; |
| private ConnectivityManager mCm; |
| private Context mContext; |
| private final static int SOCKET_TIMEOUT_MS = 100; |
| |
| @Before |
| public void setUp() throws Exception { |
| Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); |
| mContext = instrumentation.getTargetContext(); |
| mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); |
| } |
| |
| private class Connection { |
| public int socketDomain; |
| public int socketType; |
| public InetAddress localAddress; |
| public InetAddress remoteAddress; |
| public InetAddress localhostAddress; |
| public InetSocketAddress local; |
| public InetSocketAddress remote; |
| public int protocol; |
| public FileDescriptor localFd; |
| public FileDescriptor remoteFd; |
| |
| public FileDescriptor createSocket() throws Exception { |
| return Os.socket(socketDomain, socketType, protocol); |
| } |
| |
| public Connection(String to, String from) throws Exception { |
| remoteAddress = InetAddress.getByName(to); |
| if (from != null) { |
| localAddress = InetAddress.getByName(from); |
| } else { |
| localAddress = (remoteAddress instanceof Inet4Address) ? |
| Inet4Address.getByName("localhost") : Inet6Address.getByName("::"); |
| } |
| if ((localAddress instanceof Inet4Address) && (remoteAddress instanceof Inet4Address)) { |
| socketDomain = AF_INET; |
| localhostAddress = Inet4Address.getByName("localhost"); |
| } else { |
| socketDomain = AF_INET6; |
| localhostAddress = Inet6Address.getByName("::"); |
| } |
| } |
| |
| public void close() throws Exception { |
| Os.close(localFd); |
| } |
| } |
| |
| private class TcpConnection extends Connection { |
| public TcpConnection(String to, String from) throws Exception { |
| super(to, from); |
| protocol = IPPROTO_TCP; |
| socketType = SOCK_STREAM; |
| |
| remoteFd = createSocket(); |
| Os.bind(remoteFd, remoteAddress, 0); |
| Os.listen(remoteFd, 10); |
| int remotePort = ((InetSocketAddress) Os.getsockname(remoteFd)).getPort(); |
| |
| localFd = createSocket(); |
| Os.bind(localFd, localAddress, 0); |
| Os.connect(localFd, remoteAddress, remotePort); |
| |
| local = (InetSocketAddress) Os.getsockname(localFd); |
| remote = (InetSocketAddress) Os.getpeername(localFd); |
| } |
| |
| public void close() throws Exception { |
| super.close(); |
| Os.close(remoteFd); |
| } |
| } |
| private class UdpConnection extends Connection { |
| public UdpConnection(String to, String from) throws Exception { |
| super(to, from); |
| protocol = IPPROTO_UDP; |
| socketType = SOCK_DGRAM; |
| |
| remoteFd = null; |
| localFd = createSocket(); |
| Os.bind(localFd, localAddress, 0); |
| |
| Os.connect(localFd, remoteAddress, 7); |
| local = (InetSocketAddress) Os.getsockname(localFd); |
| remote = new InetSocketAddress(remoteAddress, 7); |
| } |
| } |
| |
| private void checkConnectionOwnerUid(int protocol, InetSocketAddress local, |
| InetSocketAddress remote, boolean expectSuccess) { |
| final int uid = mCm.getConnectionOwnerUid(protocol, local, remote); |
| |
| if (expectSuccess) { |
| assertEquals(Process.myUid(), uid); |
| } else { |
| assertNotEquals(Process.myUid(), uid); |
| } |
| } |
| |
| private int findLikelyFreeUdpPort(UdpConnection conn) throws Exception { |
| UdpConnection udp = new UdpConnection(conn.remoteAddress.getHostAddress(), |
| conn.localAddress.getHostAddress()); |
| final int localPort = udp.local.getPort(); |
| udp.close(); |
| return localPort; |
| } |
| |
| /** |
| * Create a test connection for UDP and TCP sockets and verify that this |
| * {protocol, local, remote} socket result in receiving a valid UID. |
| */ |
| public void checkGetConnectionOwnerUid(String to, String from) throws Exception { |
| TcpConnection tcp = new TcpConnection(to, from); |
| checkConnectionOwnerUid(tcp.protocol, tcp.local, tcp.remote, true); |
| checkConnectionOwnerUid(IPPROTO_UDP, tcp.local, tcp.remote, false); |
| checkConnectionOwnerUid(tcp.protocol, new InetSocketAddress(0), tcp.remote, false); |
| checkConnectionOwnerUid(tcp.protocol, tcp.local, new InetSocketAddress(0), false); |
| tcp.close(); |
| |
| UdpConnection udp = new UdpConnection(to,from); |
| checkConnectionOwnerUid(udp.protocol, udp.local, udp.remote, true); |
| checkConnectionOwnerUid(IPPROTO_TCP, udp.local, udp.remote, false); |
| checkConnectionOwnerUid(udp.protocol, new InetSocketAddress(findLikelyFreeUdpPort(udp)), |
| udp.remote, false); |
| udp.close(); |
| } |
| |
| @Test |
| public void testGetConnectionOwnerUid() throws Exception { |
| checkGetConnectionOwnerUid("::", null); |
| checkGetConnectionOwnerUid("::", "::"); |
| checkGetConnectionOwnerUid("0.0.0.0", null); |
| checkGetConnectionOwnerUid("0.0.0.0", "0.0.0.0"); |
| checkGetConnectionOwnerUid("127.0.0.1", null); |
| checkGetConnectionOwnerUid("127.0.0.1", "127.0.0.2"); |
| checkGetConnectionOwnerUid("::1", null); |
| checkGetConnectionOwnerUid("::1", "::1"); |
| } |
| |
| @Ignore("Times out on Marlin/Sailfish") |
| /* Verify fix for b/141603906 */ |
| @Test |
| public void testB141603906() throws Exception { |
| final InetSocketAddress src = new InetSocketAddress(0); |
| final InetSocketAddress dst = new InetSocketAddress(0); |
| for (int i = 1; i <= 100000; i++) { |
| mCm.getConnectionOwnerUid(IPPROTO_TCP, src, dst); |
| } |
| } |
| |
| // Hexadecimal representation of InetDiagReqV2 request. |
| private static final String INET_DIAG_REQ_V2_UDP_INET4_HEX = |
| // struct nlmsghdr |
| "48000000" + // length = 72 |
| "1400" + // type = SOCK_DIAG_BY_FAMILY |
| "0103" + // flags = NLM_F_REQUEST | NLM_F_DUMP |
| "00000000" + // seqno |
| "00000000" + // pid (0 == kernel) |
| // struct inet_diag_req_v2 |
| "02" + // family = AF_INET |
| "11" + // protcol = IPPROTO_UDP |
| "00" + // idiag_ext |
| "00" + // pad |
| "ffffffff" + // idiag_states |
| // inet_diag_sockid |
| "a5de" + // idiag_sport = 42462 |
| "b971" + // idiag_dport = 47473 |
| "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2 |
| "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8 |
| "00000000" + // idiag_if |
| "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE |
| private static final byte[] INET_DIAG_REQ_V2_UDP_INET4_BYTES = |
| HexEncoding.decode(INET_DIAG_REQ_V2_UDP_INET4_HEX.toCharArray(), false); |
| |
| @Test |
| public void testInetDiagReqV2UdpInet4() throws Exception { |
| InetSocketAddress local = new InetSocketAddress(InetAddress.getByName("10.0.100.2"), |
| 42462); |
| InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"), |
| 47473); |
| final byte[] msg = InetDiagMessage.InetDiagReqV2(IPPROTO_UDP, local, remote, AF_INET, |
| (short) (NLM_F_REQUEST | NLM_F_DUMP)); |
| assertArrayEquals(INET_DIAG_REQ_V2_UDP_INET4_BYTES, msg); |
| } |
| |
| // Hexadecimal representation of InetDiagReqV2 request. |
| private static final String INET_DIAG_REQ_V2_TCP_INET6_HEX = |
| // struct nlmsghdr |
| "48000000" + // length = 72 |
| "1400" + // type = SOCK_DIAG_BY_FAMILY |
| "0100" + // flags = NLM_F_REQUEST |
| "00000000" + // seqno |
| "00000000" + // pid (0 == kernel) |
| // struct inet_diag_req_v2 |
| "0a" + // family = AF_INET6 |
| "06" + // protcol = IPPROTO_TCP |
| "00" + // idiag_ext |
| "00" + // pad |
| "ffffffff" + // idiag_states |
| // inet_diag_sockid |
| "a5de" + // idiag_sport = 42462 |
| "b971" + // idiag_dport = 47473 |
| "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b |
| "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8 |
| "00000000" + // idiag_if |
| "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE |
| private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_BYTES = |
| HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_HEX.toCharArray(), false); |
| |
| @Test |
| public void testInetDiagReqV2TcpInet6() throws Exception { |
| InetSocketAddress local = new InetSocketAddress( |
| InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462); |
| InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"), |
| 47473); |
| byte[] msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6, |
| NLM_F_REQUEST); |
| |
| assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg); |
| } |
| |
| // Hexadecimal representation of InetDiagReqV2 request with extension, INET_DIAG_INFO. |
| private static final String INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_HEX = |
| // struct nlmsghdr |
| "48000000" + // length = 72 |
| "1400" + // type = SOCK_DIAG_BY_FAMILY |
| "0100" + // flags = NLM_F_REQUEST |
| "00000000" + // seqno |
| "00000000" + // pid (0 == kernel) |
| // struct inet_diag_req_v2 |
| "02" + // family = AF_INET |
| "06" + // protcol = IPPROTO_TCP |
| "02" + // idiag_ext = INET_DIAG_INFO |
| "00" + // pad |
| "ffffffff" + // idiag_states |
| // inet_diag_sockid |
| "3039" + // idiag_sport = 12345 |
| "d431" + // idiag_dport = 54321 |
| "01020304000000000000000000000000" + // idiag_src = 1.2.3.4 |
| "08080404000000000000000000000000" + // idiag_dst = 8.8.4.4 |
| "00000000" + // idiag_if |
| "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE |
| |
| private static final byte[] INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_BYTES = |
| HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_HEX.toCharArray(), false); |
| private static final int TCP_ALL_STATES = 0xffffffff; |
| @Test |
| public void testInetDiagReqV2TcpInetWithExt() throws Exception { |
| InetSocketAddress local = new InetSocketAddress( |
| InetAddress.getByName("1.2.3.4"), 12345); |
| InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.4.4"), |
| 54321); |
| byte[] msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET, |
| NLM_F_REQUEST, 0 /* pad */, 2 /* idiagExt */, TCP_ALL_STATES); |
| |
| assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_BYTES, msg); |
| |
| local = new InetSocketAddress( |
| InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462); |
| remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"), |
| 47473); |
| msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6, |
| NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES); |
| |
| assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg); |
| } |
| |
| // Hexadecimal representation of InetDiagReqV2 request with no socket specified. |
| private static final String INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_HEX = |
| // struct nlmsghdr |
| "48000000" + // length = 72 |
| "1400" + // type = SOCK_DIAG_BY_FAMILY |
| "0100" + // flags = NLM_F_REQUEST |
| "00000000" + // seqno |
| "00000000" + // pid (0 == kernel) |
| // struct inet_diag_req_v2 |
| "0a" + // family = AF_INET6 |
| "06" + // protcol = IPPROTO_TCP |
| "00" + // idiag_ext |
| "00" + // pad |
| "ffffffff" + // idiag_states |
| // inet_diag_sockid |
| "0000" + // idiag_sport |
| "0000" + // idiag_dport |
| "00000000000000000000000000000000" + // idiag_src |
| "00000000000000000000000000000000" + // idiag_dst |
| "00000000" + // idiag_if |
| "0000000000000000"; // idiag_cookie |
| |
| private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFED_BYTES = |
| HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_HEX.toCharArray(), false); |
| |
| @Test |
| public void testInetDiagReqV2TcpInet6NoIdSpecified() throws Exception { |
| InetSocketAddress local = new InetSocketAddress( |
| InetAddress.getByName("fe80::fe6a:ed4b"), 12345); |
| InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.4.4"), |
| 54321); |
| // Verify no socket specified if either local or remote socket address is null. |
| byte[] msgExt = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, null, null, AF_INET6, |
| NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES); |
| byte[] msg; |
| try { |
| msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, null, remote, AF_INET6, |
| NLM_F_REQUEST); |
| fail("Both remote and local should be null, expected UnknownHostException"); |
| } catch (NullPointerException e) { |
| } |
| |
| try { |
| msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, null, AF_INET6, |
| NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES); |
| fail("Both remote and local should be null, expected UnknownHostException"); |
| } catch (NullPointerException e) { |
| } |
| |
| msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, null, null, AF_INET6, |
| NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES); |
| assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFED_BYTES, msg); |
| assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFED_BYTES, msgExt); |
| } |
| |
| // Hexadecimal representation of InetDiagReqV2 request. |
| private static final String INET_DIAG_MSG_HEX = |
| // struct nlmsghdr |
| "58000000" + // length = 88 |
| "1400" + // type = SOCK_DIAG_BY_FAMILY |
| "0200" + // flags = NLM_F_MULTI |
| "00000000" + // seqno |
| "f5220000" + // pid (0 == kernel) |
| // struct inet_diag_msg |
| "0a" + // family = AF_INET6 |
| "01" + // idiag_state |
| "00" + // idiag_timer |
| "00" + // idiag_retrans |
| // inet_diag_sockid |
| "a817" + // idiag_sport = 43031 |
| "960f" + // idiag_dport = 38415 |
| "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b |
| "00000000000000000000ffff08080808" + // idiag_dst = 8.8.8.8 |
| "00000000" + // idiag_if |
| "ffffffffffffffff" + // idiag_cookie = INET_DIAG_NOCOOKIE |
| "00000000" + // idiag_expires |
| "00000000" + // idiag_rqueue |
| "00000000" + // idiag_wqueue |
| "a3270000" + // idiag_uid |
| "A57E1900"; // idiag_inode |
| private static final byte[] INET_DIAG_MSG_BYTES = |
| HexEncoding.decode(INET_DIAG_MSG_HEX.toCharArray(), false); |
| |
| @Test |
| public void testParseInetDiagResponse() throws Exception { |
| final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES); |
| byteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
| final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer); |
| assertNotNull(msg); |
| |
| assertTrue(msg instanceof InetDiagMessage); |
| final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg; |
| assertEquals(10147, inetDiagMsg.mStructInetDiagMsg.idiag_uid); |
| |
| final StructNlMsgHdr hdr = inetDiagMsg.getHeader(); |
| assertNotNull(hdr); |
| assertEquals(NetlinkConstants.SOCK_DIAG_BY_FAMILY, hdr.nlmsg_type); |
| assertEquals(StructNlMsgHdr.NLM_F_MULTI, hdr.nlmsg_flags); |
| assertEquals(0, hdr.nlmsg_seq); |
| assertEquals(8949, hdr.nlmsg_pid); |
| } |
| } |