Loading src/com/android/networkstack/netlink/TcpInfo.java 0 → 100644 +231 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.networkstack.netlink; import android.util.Log; import android.util.Range; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Objects; /** * Class for tcp_info. * * Corresponds to {@code struct tcp_info} from bionic/libc/kernel/uapi/linux/tcp.h */ public class TcpInfo { public enum Field { STATE(Byte.BYTES), CASTATE(Byte.BYTES), RETRANSMITS(Byte.BYTES), PROBES(Byte.BYTES), BACKOFF(Byte.BYTES), OPTIONS(Byte.BYTES), WSCALE(Byte.BYTES), DELIVERY_RATE_APP_LIMITED(Byte.BYTES), RTO(Integer.BYTES), ATO(Integer.BYTES), SND_MSS(Integer.BYTES), RCV_MSS(Integer.BYTES), UNACKED(Integer.BYTES), SACKED(Integer.BYTES), LOST(Integer.BYTES), RETRANS(Integer.BYTES), FACKETS(Integer.BYTES), LAST_DATA_SENT(Integer.BYTES), LAST_ACK_SENT(Integer.BYTES), LAST_DATA_RECV(Integer.BYTES), LAST_ACK_RECV(Integer.BYTES), PMTU(Integer.BYTES), RCV_SSTHRESH(Integer.BYTES), RTT(Integer.BYTES), RTTVAR(Integer.BYTES), SND_SSTHRESH(Integer.BYTES), SND_CWND(Integer.BYTES), ADVMSS(Integer.BYTES), REORDERING(Integer.BYTES), RCV_RTT(Integer.BYTES), RCV_SPACE(Integer.BYTES), TOTAL_RETRANS(Integer.BYTES), PACING_RATE(Long.BYTES), MAX_PACING_RATE(Long.BYTES), BYTES_ACKED(Long.BYTES), BYTES_RECEIVED(Long.BYTES), SEGS_OUT(Integer.BYTES), SEGS_IN(Integer.BYTES), NOTSENT_BYTES(Integer.BYTES), MIN_RTT(Integer.BYTES), DATA_SEGS_IN(Integer.BYTES), DATA_SEGS_OUT(Integer.BYTES), DELIVERY_RATE(Long.BYTES), BUSY_TIME(Long.BYTES), RWND_LIMITED(Long.BYTES), SNDBUF_LIMITED(Long.BYTES); public final int size; Field(int s) { size = s; } } private static final String TAG = "TcpInfo"; private final LinkedHashMap<Field, Number> mFieldsValues = new LinkedHashMap<Field, Number>(); private TcpInfo(@NonNull ByteBuffer bytes, int infolen) { final int start = bytes.position(); for (final Field field : Field.values()) { switch (field.size) { case Byte.BYTES: mFieldsValues.put(field, getByte(bytes, start, infolen)); break; case Integer.BYTES: mFieldsValues.put(field, getInt(bytes, start, infolen)); break; case Long.BYTES: mFieldsValues.put(field, getLong(bytes, start, infolen)); break; default: Log.e(TAG, "Unexpected size:" + field.size); } } } @VisibleForTesting public TcpInfo(@NonNull HashMap<Field, Number> info) { for (final Field field : Field.values()) { mFieldsValues.put(field, info.get(field)); } } /** Parse a TcpInfo from a giving ByteBuffer with a specific length. */ @Nullable public static TcpInfo parse(@NonNull ByteBuffer bytes, int infolen) { try { TcpInfo info = new TcpInfo(bytes, infolen); return info; } catch (BufferUnderflowException e) { Log.e(TAG, "parsing error.", e); return null; } } /** * Helper function for handling different struct tcp_info versions in the kernel. */ private static boolean isValidOffset(int start, int len, int pos, int targetBytes) { final Range a = new Range(start, start + len); final Range b = new Range(pos, pos + targetBytes); return a.contains(b); } /** Get value for specific key. */ @Nullable public Number getValue(@NonNull Field key) { return mFieldsValues.get(key); } @Nullable private static Byte getByte(@NonNull ByteBuffer buffer, int start, int len) { if (!isValidOffset(start, len, buffer.position(), Byte.BYTES)) return null; return buffer.get(); } @Nullable private static Integer getInt(@NonNull ByteBuffer buffer, int start, int len) { if (!isValidOffset(start, len, buffer.position(), Integer.BYTES)) return null; return buffer.getInt(); } @Nullable private static Long getLong(@NonNull ByteBuffer buffer, int start, int len) { if (!isValidOffset(start, len, buffer.position(), Long.BYTES)) return null; return buffer.getLong(); } private static String decodeWscale(byte num) { return String.valueOf((num >> 4) & 0x0f) + ":" + String.valueOf(num & 0x0f); } /** * Returns a string representing a given tcp state. * Map to enum in bionic/libc/include/netinet/tcp.h */ @VisibleForTesting public static String getTcpStateName(int state) { switch (state) { case 1: return "TCP_ESTABLISHED"; case 2: return "TCP_SYN_SENT"; case 3: return "TCP_SYN_RECV"; case 4: return "TCP_FIN_WAIT1"; case 5: return "TCP_FIN_WAIT2"; case 6: return "TCP_TIME_WAIT"; case 7: return "TCP_CLOSE"; case 8: return "TCP_CLOSE_WAIT"; case 9: return "TCP_LAST_ACK"; case 10: return "TCP_LISTEN"; case 11: return "TCP_CLOSING"; default: return "UNKNOWN:" + Integer.toString(state); } } @Override public boolean equals(Object obj) { if (!(obj instanceof TcpInfo)) return false; TcpInfo other = (TcpInfo) obj; for (final Field key : mFieldsValues.keySet()) { if (!Objects.equals(mFieldsValues.get(key), other.mFieldsValues.get(key))) { return false; } } return true; } @Override public int hashCode() { return Objects.hash(mFieldsValues.values().toArray()); } @Override public String toString() { String str = "TcpInfo{ "; for (final Field key : mFieldsValues.keySet()) { str += key.name().toLowerCase() + "="; if (key == Field.STATE) { str += getTcpStateName(mFieldsValues.get(key).intValue()) + " "; } else if (key == Field.WSCALE) { str += decodeWscale(mFieldsValues.get(key).byteValue()) + " "; } else { str += mFieldsValues.get(key) + " "; } } str += "}"; return str; } } tests/unit/src/android/networkstack/netlink/TcpInfoTest.java 0 → 100644 +227 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.networkstack.netlink; import static org.junit.Assert.assertEquals; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.networkstack.netlink.TcpInfo; import libcore.util.HexEncoding; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.ByteBuffer; import java.util.HashMap; @RunWith(AndroidJUnit4.class) @SmallTest public class TcpInfoTest { private static final int TCP_INFO_LENGTH_V1 = 192; private static final int SHORT_TEST_TCP_INFO = 8; private static final String TCP_ESTABLISHED = "TCP_ESTABLISHED"; private static final String TCP_FIN_WAIT1 = "TCP_FIN_WAIT1"; private static final String TCP_SYN_SENT = "TCP_SYN_SENT"; private static final String UNKNOWN_20 = "UNKNOWN:20"; // Refer to rfc793 for the value definition. private static final String TCP_INFO_HEX = "01" + // state = TCP_ESTABLISHED "00" + // ca_state = TCP_CA_OPEN "00" + // retransmits = 0 "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 "001B914A" + // rto = 1806666 "00000000" + // ato = 0 "0000052E" + // sndMss = 1326 "00000218" + // rcvMss = 536 "00000000" + // unsacked = 0 "00000000" + // acked = 0 "00000000" + // lost = 0 "00000000" + // retrans = 0 "00000000" + // fackets = 0 "000000BB" + // lastDataSent = 187 "00000000" + // lastAckSent = 0 "000000BB" + // lastDataRecv = 187 "000000BB" + // lastDataAckRecv = 187 "000005DC" + // pmtu = 1500 "00015630" + // rcvSsthresh = 87600 "00092C3E" + // rttt = 601150 "0004961F" + // rttvar = 300575 "00000578" + // sndSsthresh = 1400 "0000000A" + // sndCwnd = 10 "000005A8" + // advmss = 1448 "00000003" + // reordering = 3 "00000000" + // rcvrtt = 0 "00015630" + // rcvspace = 87600 "00000000" + // totalRetrans = 0 "000000000000AC53" + // pacingRate = 44115 "FFFFFFFFFFFFFFFF" + // maxPacingRate = 18446744073709551615 "0000000000000001" + // bytesAcked = 1 "0000000000000000" + // bytesReceived = 0 "00000002" + // SegsOut = 2 "00000001" + // SegsIn = 1 "00000000" + // NotSentBytes = 0 "00092C3E" + // minRtt = 601150 "00000000" + // DataSegsIn = 0 "00000000" + // DataSegsOut = 0 "0000000000000000" + // deliverRate = 0 "0000000000000000" + // busyTime = 0 "0000000000000000" + // RwndLimited = 0 "0000000000000000"; // sndBufLimited = 0 private static final byte[] TCP_INFO_BYTES = HexEncoding.decode(TCP_INFO_HEX.toCharArray(), false); @Test public void testParseTcpInfo() { final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES); final HashMap<TcpInfo.Field, Number> expected = makeTestTcpInfoHash(); final TcpInfo parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1); assertEquals(parsedInfo, new TcpInfo(expected)); } @Test public void testValidOffset() { final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES); final HashMap<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash(); final TcpInfo parsedInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO); assertEquals(parsedInfo, new TcpInfo(expected)); } @Test public void testTcpStateName() { assertEquals(TcpInfo.getTcpStateName(4), TCP_FIN_WAIT1); assertEquals(TcpInfo.getTcpStateName(1), TCP_ESTABLISHED); assertEquals(TcpInfo.getTcpStateName(2), TCP_SYN_SENT); assertEquals(TcpInfo.getTcpStateName(20), UNKNOWN_20); } private static final String MALFORMED_TCP_INFO_HEX = "01" + // state = TCP_ESTABLISHED "00" + // ca_state = TCP_CA_OPEN "00" + // retransmits = 0 "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 "001B"; // Incomplete bytes, expect to be an int. private static final byte[] MALFORMED_TCP_INFO_BYTES = HexEncoding.decode(MALFORMED_TCP_INFO_HEX.toCharArray(), false); @Test public void testMalformedTcpInfo() { final ByteBuffer buffer = ByteBuffer.wrap(MALFORMED_TCP_INFO_BYTES); final HashMap<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash(); TcpInfo parsedInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO); assertEquals(parsedInfo, new TcpInfo(expected)); parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1); assertEquals(parsedInfo, null); } @Test public void testGetValue() { ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES); final HashMap<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash(); expected.put(TcpInfo.Field.MAX_PACING_RATE, 10_000L); expected.put(TcpInfo.Field.FACKETS, 10); final TcpInfo expectedInfo = new TcpInfo(expected); assertEquals((byte) 0x01, expectedInfo.getValue(TcpInfo.Field.STATE)); assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.CASTATE)); assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.RETRANSMITS)); assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.PROBES)); assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.BACKOFF)); assertEquals((byte) 0x07, expectedInfo.getValue(TcpInfo.Field.OPTIONS)); assertEquals((byte) 0x88, expectedInfo.getValue(TcpInfo.Field.WSCALE)); assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.DELIVERY_RATE_APP_LIMITED)); assertEquals(10_000L, expectedInfo.getValue(TcpInfo.Field.MAX_PACING_RATE)); assertEquals(10, expectedInfo.getValue(TcpInfo.Field.FACKETS)); assertEquals(null, expectedInfo.getValue(TcpInfo.Field.RTT)); } // Make a TcpInfo contains only first 8 bytes. private HashMap<TcpInfo.Field, Number> makeShortTestTcpInfoHash() { final HashMap<TcpInfo.Field, Number> info = new HashMap<TcpInfo.Field, Number>(); info.put(TcpInfo.Field.STATE, (byte) 0x01); info.put(TcpInfo.Field.CASTATE, (byte) 0x00); info.put(TcpInfo.Field.RETRANSMITS, (byte) 0x00); info.put(TcpInfo.Field.PROBES, (byte) 0x00); info.put(TcpInfo.Field.BACKOFF, (byte) 0x00); info.put(TcpInfo.Field.OPTIONS, (byte) 0x07); info.put(TcpInfo.Field.WSCALE, (byte) 0x88); info.put(TcpInfo.Field.DELIVERY_RATE_APP_LIMITED, (byte) 0x00); return info; } private HashMap<TcpInfo.Field, Number> makeTestTcpInfoHash() { final HashMap<TcpInfo.Field, Number> info = makeShortTestTcpInfoHash(); info.put(TcpInfo.Field.RTO, 1806666); info.put(TcpInfo.Field.ATO, 0); info.put(TcpInfo.Field.SND_MSS, 1326); info.put(TcpInfo.Field.RCV_MSS, 536); info.put(TcpInfo.Field.UNACKED, 0); info.put(TcpInfo.Field.SACKED, 0); info.put(TcpInfo.Field.LOST, 0); info.put(TcpInfo.Field.RETRANS, 0); info.put(TcpInfo.Field.FACKETS, 0); info.put(TcpInfo.Field.LAST_DATA_SENT, 187); info.put(TcpInfo.Field.LAST_ACK_SENT, 0); info.put(TcpInfo.Field.LAST_DATA_RECV, 187); info.put(TcpInfo.Field.LAST_ACK_RECV, 187); info.put(TcpInfo.Field.PMTU, 1500); info.put(TcpInfo.Field.RCV_SSTHRESH, 87600); info.put(TcpInfo.Field.RTT, 601150); info.put(TcpInfo.Field.RTTVAR, 300575); info.put(TcpInfo.Field.SND_SSTHRESH, 1400); info.put(TcpInfo.Field.SND_CWND, 10); info.put(TcpInfo.Field.ADVMSS, 1448); info.put(TcpInfo.Field.REORDERING, 3); info.put(TcpInfo.Field.RCV_RTT, 0); info.put(TcpInfo.Field.RCV_SPACE, 87600); info.put(TcpInfo.Field.TOTAL_RETRANS, 0); info.put(TcpInfo.Field.PACING_RATE, 44115L); info.put(TcpInfo.Field.MAX_PACING_RATE, -1L); info.put(TcpInfo.Field.BYTES_ACKED, 1L); info.put(TcpInfo.Field.BYTES_RECEIVED, 0L); info.put(TcpInfo.Field.SEGS_OUT, 2); info.put(TcpInfo.Field.SEGS_IN, 1); info.put(TcpInfo.Field.NOTSENT_BYTES, 0); info.put(TcpInfo.Field.MIN_RTT, 601150); info.put(TcpInfo.Field.DATA_SEGS_IN, 0); info.put(TcpInfo.Field.DATA_SEGS_OUT, 0); info.put(TcpInfo.Field.DELIVERY_RATE, 0L); info.put(TcpInfo.Field.BUSY_TIME, 0L); info.put(TcpInfo.Field.RWND_LIMITED, 0L); info.put(TcpInfo.Field.SNDBUF_LIMITED, 0L); return info; } } Loading
src/com/android/networkstack/netlink/TcpInfo.java 0 → 100644 +231 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.networkstack.netlink; import android.util.Log; import android.util.Range; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Objects; /** * Class for tcp_info. * * Corresponds to {@code struct tcp_info} from bionic/libc/kernel/uapi/linux/tcp.h */ public class TcpInfo { public enum Field { STATE(Byte.BYTES), CASTATE(Byte.BYTES), RETRANSMITS(Byte.BYTES), PROBES(Byte.BYTES), BACKOFF(Byte.BYTES), OPTIONS(Byte.BYTES), WSCALE(Byte.BYTES), DELIVERY_RATE_APP_LIMITED(Byte.BYTES), RTO(Integer.BYTES), ATO(Integer.BYTES), SND_MSS(Integer.BYTES), RCV_MSS(Integer.BYTES), UNACKED(Integer.BYTES), SACKED(Integer.BYTES), LOST(Integer.BYTES), RETRANS(Integer.BYTES), FACKETS(Integer.BYTES), LAST_DATA_SENT(Integer.BYTES), LAST_ACK_SENT(Integer.BYTES), LAST_DATA_RECV(Integer.BYTES), LAST_ACK_RECV(Integer.BYTES), PMTU(Integer.BYTES), RCV_SSTHRESH(Integer.BYTES), RTT(Integer.BYTES), RTTVAR(Integer.BYTES), SND_SSTHRESH(Integer.BYTES), SND_CWND(Integer.BYTES), ADVMSS(Integer.BYTES), REORDERING(Integer.BYTES), RCV_RTT(Integer.BYTES), RCV_SPACE(Integer.BYTES), TOTAL_RETRANS(Integer.BYTES), PACING_RATE(Long.BYTES), MAX_PACING_RATE(Long.BYTES), BYTES_ACKED(Long.BYTES), BYTES_RECEIVED(Long.BYTES), SEGS_OUT(Integer.BYTES), SEGS_IN(Integer.BYTES), NOTSENT_BYTES(Integer.BYTES), MIN_RTT(Integer.BYTES), DATA_SEGS_IN(Integer.BYTES), DATA_SEGS_OUT(Integer.BYTES), DELIVERY_RATE(Long.BYTES), BUSY_TIME(Long.BYTES), RWND_LIMITED(Long.BYTES), SNDBUF_LIMITED(Long.BYTES); public final int size; Field(int s) { size = s; } } private static final String TAG = "TcpInfo"; private final LinkedHashMap<Field, Number> mFieldsValues = new LinkedHashMap<Field, Number>(); private TcpInfo(@NonNull ByteBuffer bytes, int infolen) { final int start = bytes.position(); for (final Field field : Field.values()) { switch (field.size) { case Byte.BYTES: mFieldsValues.put(field, getByte(bytes, start, infolen)); break; case Integer.BYTES: mFieldsValues.put(field, getInt(bytes, start, infolen)); break; case Long.BYTES: mFieldsValues.put(field, getLong(bytes, start, infolen)); break; default: Log.e(TAG, "Unexpected size:" + field.size); } } } @VisibleForTesting public TcpInfo(@NonNull HashMap<Field, Number> info) { for (final Field field : Field.values()) { mFieldsValues.put(field, info.get(field)); } } /** Parse a TcpInfo from a giving ByteBuffer with a specific length. */ @Nullable public static TcpInfo parse(@NonNull ByteBuffer bytes, int infolen) { try { TcpInfo info = new TcpInfo(bytes, infolen); return info; } catch (BufferUnderflowException e) { Log.e(TAG, "parsing error.", e); return null; } } /** * Helper function for handling different struct tcp_info versions in the kernel. */ private static boolean isValidOffset(int start, int len, int pos, int targetBytes) { final Range a = new Range(start, start + len); final Range b = new Range(pos, pos + targetBytes); return a.contains(b); } /** Get value for specific key. */ @Nullable public Number getValue(@NonNull Field key) { return mFieldsValues.get(key); } @Nullable private static Byte getByte(@NonNull ByteBuffer buffer, int start, int len) { if (!isValidOffset(start, len, buffer.position(), Byte.BYTES)) return null; return buffer.get(); } @Nullable private static Integer getInt(@NonNull ByteBuffer buffer, int start, int len) { if (!isValidOffset(start, len, buffer.position(), Integer.BYTES)) return null; return buffer.getInt(); } @Nullable private static Long getLong(@NonNull ByteBuffer buffer, int start, int len) { if (!isValidOffset(start, len, buffer.position(), Long.BYTES)) return null; return buffer.getLong(); } private static String decodeWscale(byte num) { return String.valueOf((num >> 4) & 0x0f) + ":" + String.valueOf(num & 0x0f); } /** * Returns a string representing a given tcp state. * Map to enum in bionic/libc/include/netinet/tcp.h */ @VisibleForTesting public static String getTcpStateName(int state) { switch (state) { case 1: return "TCP_ESTABLISHED"; case 2: return "TCP_SYN_SENT"; case 3: return "TCP_SYN_RECV"; case 4: return "TCP_FIN_WAIT1"; case 5: return "TCP_FIN_WAIT2"; case 6: return "TCP_TIME_WAIT"; case 7: return "TCP_CLOSE"; case 8: return "TCP_CLOSE_WAIT"; case 9: return "TCP_LAST_ACK"; case 10: return "TCP_LISTEN"; case 11: return "TCP_CLOSING"; default: return "UNKNOWN:" + Integer.toString(state); } } @Override public boolean equals(Object obj) { if (!(obj instanceof TcpInfo)) return false; TcpInfo other = (TcpInfo) obj; for (final Field key : mFieldsValues.keySet()) { if (!Objects.equals(mFieldsValues.get(key), other.mFieldsValues.get(key))) { return false; } } return true; } @Override public int hashCode() { return Objects.hash(mFieldsValues.values().toArray()); } @Override public String toString() { String str = "TcpInfo{ "; for (final Field key : mFieldsValues.keySet()) { str += key.name().toLowerCase() + "="; if (key == Field.STATE) { str += getTcpStateName(mFieldsValues.get(key).intValue()) + " "; } else if (key == Field.WSCALE) { str += decodeWscale(mFieldsValues.get(key).byteValue()) + " "; } else { str += mFieldsValues.get(key) + " "; } } str += "}"; return str; } }
tests/unit/src/android/networkstack/netlink/TcpInfoTest.java 0 → 100644 +227 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.networkstack.netlink; import static org.junit.Assert.assertEquals; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.networkstack.netlink.TcpInfo; import libcore.util.HexEncoding; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.ByteBuffer; import java.util.HashMap; @RunWith(AndroidJUnit4.class) @SmallTest public class TcpInfoTest { private static final int TCP_INFO_LENGTH_V1 = 192; private static final int SHORT_TEST_TCP_INFO = 8; private static final String TCP_ESTABLISHED = "TCP_ESTABLISHED"; private static final String TCP_FIN_WAIT1 = "TCP_FIN_WAIT1"; private static final String TCP_SYN_SENT = "TCP_SYN_SENT"; private static final String UNKNOWN_20 = "UNKNOWN:20"; // Refer to rfc793 for the value definition. private static final String TCP_INFO_HEX = "01" + // state = TCP_ESTABLISHED "00" + // ca_state = TCP_CA_OPEN "00" + // retransmits = 0 "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 "001B914A" + // rto = 1806666 "00000000" + // ato = 0 "0000052E" + // sndMss = 1326 "00000218" + // rcvMss = 536 "00000000" + // unsacked = 0 "00000000" + // acked = 0 "00000000" + // lost = 0 "00000000" + // retrans = 0 "00000000" + // fackets = 0 "000000BB" + // lastDataSent = 187 "00000000" + // lastAckSent = 0 "000000BB" + // lastDataRecv = 187 "000000BB" + // lastDataAckRecv = 187 "000005DC" + // pmtu = 1500 "00015630" + // rcvSsthresh = 87600 "00092C3E" + // rttt = 601150 "0004961F" + // rttvar = 300575 "00000578" + // sndSsthresh = 1400 "0000000A" + // sndCwnd = 10 "000005A8" + // advmss = 1448 "00000003" + // reordering = 3 "00000000" + // rcvrtt = 0 "00015630" + // rcvspace = 87600 "00000000" + // totalRetrans = 0 "000000000000AC53" + // pacingRate = 44115 "FFFFFFFFFFFFFFFF" + // maxPacingRate = 18446744073709551615 "0000000000000001" + // bytesAcked = 1 "0000000000000000" + // bytesReceived = 0 "00000002" + // SegsOut = 2 "00000001" + // SegsIn = 1 "00000000" + // NotSentBytes = 0 "00092C3E" + // minRtt = 601150 "00000000" + // DataSegsIn = 0 "00000000" + // DataSegsOut = 0 "0000000000000000" + // deliverRate = 0 "0000000000000000" + // busyTime = 0 "0000000000000000" + // RwndLimited = 0 "0000000000000000"; // sndBufLimited = 0 private static final byte[] TCP_INFO_BYTES = HexEncoding.decode(TCP_INFO_HEX.toCharArray(), false); @Test public void testParseTcpInfo() { final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES); final HashMap<TcpInfo.Field, Number> expected = makeTestTcpInfoHash(); final TcpInfo parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1); assertEquals(parsedInfo, new TcpInfo(expected)); } @Test public void testValidOffset() { final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES); final HashMap<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash(); final TcpInfo parsedInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO); assertEquals(parsedInfo, new TcpInfo(expected)); } @Test public void testTcpStateName() { assertEquals(TcpInfo.getTcpStateName(4), TCP_FIN_WAIT1); assertEquals(TcpInfo.getTcpStateName(1), TCP_ESTABLISHED); assertEquals(TcpInfo.getTcpStateName(2), TCP_SYN_SENT); assertEquals(TcpInfo.getTcpStateName(20), UNKNOWN_20); } private static final String MALFORMED_TCP_INFO_HEX = "01" + // state = TCP_ESTABLISHED "00" + // ca_state = TCP_CA_OPEN "00" + // retransmits = 0 "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 "001B"; // Incomplete bytes, expect to be an int. private static final byte[] MALFORMED_TCP_INFO_BYTES = HexEncoding.decode(MALFORMED_TCP_INFO_HEX.toCharArray(), false); @Test public void testMalformedTcpInfo() { final ByteBuffer buffer = ByteBuffer.wrap(MALFORMED_TCP_INFO_BYTES); final HashMap<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash(); TcpInfo parsedInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO); assertEquals(parsedInfo, new TcpInfo(expected)); parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1); assertEquals(parsedInfo, null); } @Test public void testGetValue() { ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES); final HashMap<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash(); expected.put(TcpInfo.Field.MAX_PACING_RATE, 10_000L); expected.put(TcpInfo.Field.FACKETS, 10); final TcpInfo expectedInfo = new TcpInfo(expected); assertEquals((byte) 0x01, expectedInfo.getValue(TcpInfo.Field.STATE)); assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.CASTATE)); assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.RETRANSMITS)); assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.PROBES)); assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.BACKOFF)); assertEquals((byte) 0x07, expectedInfo.getValue(TcpInfo.Field.OPTIONS)); assertEquals((byte) 0x88, expectedInfo.getValue(TcpInfo.Field.WSCALE)); assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.DELIVERY_RATE_APP_LIMITED)); assertEquals(10_000L, expectedInfo.getValue(TcpInfo.Field.MAX_PACING_RATE)); assertEquals(10, expectedInfo.getValue(TcpInfo.Field.FACKETS)); assertEquals(null, expectedInfo.getValue(TcpInfo.Field.RTT)); } // Make a TcpInfo contains only first 8 bytes. private HashMap<TcpInfo.Field, Number> makeShortTestTcpInfoHash() { final HashMap<TcpInfo.Field, Number> info = new HashMap<TcpInfo.Field, Number>(); info.put(TcpInfo.Field.STATE, (byte) 0x01); info.put(TcpInfo.Field.CASTATE, (byte) 0x00); info.put(TcpInfo.Field.RETRANSMITS, (byte) 0x00); info.put(TcpInfo.Field.PROBES, (byte) 0x00); info.put(TcpInfo.Field.BACKOFF, (byte) 0x00); info.put(TcpInfo.Field.OPTIONS, (byte) 0x07); info.put(TcpInfo.Field.WSCALE, (byte) 0x88); info.put(TcpInfo.Field.DELIVERY_RATE_APP_LIMITED, (byte) 0x00); return info; } private HashMap<TcpInfo.Field, Number> makeTestTcpInfoHash() { final HashMap<TcpInfo.Field, Number> info = makeShortTestTcpInfoHash(); info.put(TcpInfo.Field.RTO, 1806666); info.put(TcpInfo.Field.ATO, 0); info.put(TcpInfo.Field.SND_MSS, 1326); info.put(TcpInfo.Field.RCV_MSS, 536); info.put(TcpInfo.Field.UNACKED, 0); info.put(TcpInfo.Field.SACKED, 0); info.put(TcpInfo.Field.LOST, 0); info.put(TcpInfo.Field.RETRANS, 0); info.put(TcpInfo.Field.FACKETS, 0); info.put(TcpInfo.Field.LAST_DATA_SENT, 187); info.put(TcpInfo.Field.LAST_ACK_SENT, 0); info.put(TcpInfo.Field.LAST_DATA_RECV, 187); info.put(TcpInfo.Field.LAST_ACK_RECV, 187); info.put(TcpInfo.Field.PMTU, 1500); info.put(TcpInfo.Field.RCV_SSTHRESH, 87600); info.put(TcpInfo.Field.RTT, 601150); info.put(TcpInfo.Field.RTTVAR, 300575); info.put(TcpInfo.Field.SND_SSTHRESH, 1400); info.put(TcpInfo.Field.SND_CWND, 10); info.put(TcpInfo.Field.ADVMSS, 1448); info.put(TcpInfo.Field.REORDERING, 3); info.put(TcpInfo.Field.RCV_RTT, 0); info.put(TcpInfo.Field.RCV_SPACE, 87600); info.put(TcpInfo.Field.TOTAL_RETRANS, 0); info.put(TcpInfo.Field.PACING_RATE, 44115L); info.put(TcpInfo.Field.MAX_PACING_RATE, -1L); info.put(TcpInfo.Field.BYTES_ACKED, 1L); info.put(TcpInfo.Field.BYTES_RECEIVED, 0L); info.put(TcpInfo.Field.SEGS_OUT, 2); info.put(TcpInfo.Field.SEGS_IN, 1); info.put(TcpInfo.Field.NOTSENT_BYTES, 0); info.put(TcpInfo.Field.MIN_RTT, 601150); info.put(TcpInfo.Field.DATA_SEGS_IN, 0); info.put(TcpInfo.Field.DATA_SEGS_OUT, 0); info.put(TcpInfo.Field.DELIVERY_RATE, 0L); info.put(TcpInfo.Field.BUSY_TIME, 0L); info.put(TcpInfo.Field.RWND_LIMITED, 0L); info.put(TcpInfo.Field.SNDBUF_LIMITED, 0L); return info; } }