diff options
author | Chiachang Wang <chiachangwang@google.com> | 2020-02-12 09:28:01 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2020-02-12 09:28:01 +0000 |
commit | 67ab467166a0343a6dc27a7c9adb04d877d9f40b (patch) | |
tree | 2e9de24715ff8e404e70442209e57f8d5148cbef | |
parent | 1852ead9d9039afdd39036d93fed0c5a30ced3dc (diff) | |
parent | 85b49af029494009da6574cfa275758585da5301 (diff) |
Merge "update structure of TcpInfo"
4 files changed, 81 insertions, 185 deletions
diff --git a/src/com/android/networkstack/netlink/TcpInfo.java b/src/com/android/networkstack/netlink/TcpInfo.java index e6036b5..31a408f 100644 --- a/src/com/android/networkstack/netlink/TcpInfo.java +++ b/src/com/android/networkstack/netlink/TcpInfo.java @@ -22,11 +22,9 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.Objects; /** @@ -91,27 +89,39 @@ public class TcpInfo { } private static final String TAG = "TcpInfo"; - private final Map<Field, Number> mFieldsValues; + @VisibleForTesting + static final int LOST_OFFSET = getFieldOffset(Field.LOST); + @VisibleForTesting + static final int RETRANSMITS_OFFSET = getFieldOffset(Field.RETRANSMITS); + @VisibleForTesting + static final int SEGS_IN_OFFSET = getFieldOffset(Field.SEGS_IN); + @VisibleForTesting + static final int SEGS_OUT_OFFSET = getFieldOffset(Field.SEGS_OUT); + final int mSegsIn; + final int mSegsOut; + final int mLost; + final int mRetransmits; + + private static int getFieldOffset(@NonNull final Field needle) { + int offset = 0; + for (final Field field : Field.values()) { + if (field == needle) return offset; + offset += field.size; + } + throw new IllegalArgumentException("Unknown field"); + } private TcpInfo(@NonNull ByteBuffer bytes, int infolen) { - final int start = bytes.position(); - final LinkedHashMap<Field, Number> fields = new LinkedHashMap<>(); - for (final Field field : Field.values()) { - switch (field.size) { - case Byte.BYTES: - fields.put(field, getByte(bytes, start, infolen)); - break; - case Integer.BYTES: - fields.put(field, getInt(bytes, start, infolen)); - break; - case Long.BYTES: - fields.put(field, getLong(bytes, start, infolen)); - break; - default: - Log.e(TAG, "Unexpected size:" + field.size); - } + // SEGS_IN is the last required field in the buffer, so if the buffer is long enough for + // SEGS_IN it's long enough for everything + if (SEGS_IN_OFFSET + Field.SEGS_IN.size > infolen) { + throw new IllegalArgumentException("Length " + infolen + " is less than required."); } - mFieldsValues = Collections.unmodifiableMap(fields); + final int start = bytes.position(); + mSegsIn = bytes.getInt(start + SEGS_IN_OFFSET); + mSegsOut = bytes.getInt(start + SEGS_OUT_OFFSET); + mLost = bytes.getInt(start + LOST_OFFSET); + mRetransmits = bytes.get(start + RETRANSMITS_OFFSET); // tcp_info structure grows over time as new fields are added. Jump to the end of the // structure, as unknown fields might remain at the end of the structure if the tcp_info // struct was expanded. @@ -119,12 +129,11 @@ public class TcpInfo { } @VisibleForTesting - TcpInfo(@NonNull Map<Field, Number> info) { - final LinkedHashMap<Field, Number> fields = new LinkedHashMap<>(); - for (final Field field : Field.values()) { - fields.put(field, info.get(field)); - } - mFieldsValues = Collections.unmodifiableMap(fields); + TcpInfo(int retransmits, int lost, int segsOut, int segsIn) { + mRetransmits = retransmits; + mLost = lost; + mSegsOut = segsOut; + mSegsIn = segsIn; } /** Parse a TcpInfo from a giving ByteBuffer with a specific length. */ @@ -132,53 +141,13 @@ public class TcpInfo { public static TcpInfo parse(@NonNull ByteBuffer bytes, int infolen) { try { return new TcpInfo(bytes, infolen); - } catch (BufferUnderflowException | IllegalArgumentException e) { + } catch (BufferUnderflowException | BufferOverflowException | IllegalArgumentException + | IndexOutOfBoundsException e) { Log.e(TAG, "parsing error.", e); return null; } } - /** - * Helper function for handling different struct tcp_info versions in the kernel. - */ - private static boolean isValidTargetPosition(int start, int len, int pos, int targetBytes) - throws IllegalArgumentException { - // Equivalent to new Range(start, start + len).contains(new Range(pos, pos + targetBytes)) - if (len < 0 || targetBytes < 0) throw new IllegalArgumentException(); - // Check that start < pos < start + len - if (pos < start || pos > start + len) return false; - // Pos is inside the range and targetBytes is positive. Offset is valid if end of 2nd range - // is below end of 1st range. - return pos + targetBytes <= start + len; - } - - /** 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 (!isValidTargetPosition(start, len, buffer.position(), Byte.BYTES)) return null; - - return buffer.get(); - } - - @Nullable - private static Integer getInt(@NonNull ByteBuffer buffer, int start, int len) { - if (!isValidTargetPosition(start, len, buffer.position(), Integer.BYTES)) return null; - - return buffer.getInt(); - } - - @Nullable - private static Long getLong(@NonNull ByteBuffer buffer, int start, int len) { - if (!isValidTargetPosition(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); } @@ -210,33 +179,18 @@ public class TcpInfo { 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; + return mSegsIn == other.mSegsIn && mSegsOut == other.mSegsOut + && mRetransmits == other.mRetransmits && mLost == other.mLost; } @Override public int hashCode() { - return Objects.hash(mFieldsValues.values().toArray()); + return Objects.hash(mLost, mRetransmits, mSegsIn, mSegsOut); } @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; + return "TcpInfo{lost=" + mLost + ", retransmit=" + mRetransmits + ", received=" + mSegsIn + + ", sent=" + mSegsOut + "}"; } } diff --git a/src/com/android/networkstack/netlink/TcpSocketTracker.java b/src/com/android/networkstack/netlink/TcpSocketTracker.java index 78813bd..f660f81 100644 --- a/src/com/android/networkstack/netlink/TcpSocketTracker.java +++ b/src/com/android/networkstack/netlink/TcpSocketTracker.java @@ -340,16 +340,16 @@ public class TcpSocketTracker { return null; } - stat.sentCount = current.tcpInfo.getValue(TcpInfo.Field.SEGS_OUT).intValue(); - stat.receivedCount = current.tcpInfo.getValue(TcpInfo.Field.SEGS_IN).intValue(); - stat.lostCount = current.tcpInfo.getValue(TcpInfo.Field.LOST).intValue(); - stat.retransmitCount = current.tcpInfo.getValue(TcpInfo.Field.RETRANSMITS).intValue(); + stat.sentCount = current.tcpInfo.mSegsOut; + stat.receivedCount = current.tcpInfo.mSegsIn; + stat.lostCount = current.tcpInfo.mLost; + stat.retransmitCount = current.tcpInfo.mRetransmits; if (previous != null && previous.tcpInfo != null) { - stat.sentCount -= previous.tcpInfo.getValue(TcpInfo.Field.SEGS_OUT).intValue(); - stat.receivedCount -= previous.tcpInfo.getValue(TcpInfo.Field.SEGS_IN).intValue(); - stat.lostCount -= previous.tcpInfo.getValue(TcpInfo.Field.LOST).intValue(); - stat.retransmitCount -= previous.tcpInfo.getValue(TcpInfo.Field.RETRANSMITS).intValue(); + stat.sentCount -= previous.tcpInfo.mSegsOut; + stat.receivedCount -= previous.tcpInfo.mSegsIn; + stat.lostCount -= previous.tcpInfo.mLost; + stat.retransmitCount -= previous.tcpInfo.mRetransmits; } return stat; diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java index f65de9c..ddab8c7 100644 --- a/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java +++ b/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java @@ -89,6 +89,8 @@ public class TcpInfoTest { "0000000000000000"; // sndBufLimited = 0 private static final byte[] TCP_INFO_BYTES = HexEncoding.decode(TCP_INFO_HEX.toCharArray(), false); + private static final TcpInfo TEST_TCPINFO = + new TcpInfo(0 /* retransmits */, 0 /* lost */, 2 /* segsOut */, 1 /* segsIn */); private static final String EXPANDED_TCP_INFO_HEX = TCP_INFO_HEX + "00000000" // tcpi_delivered @@ -100,40 +102,48 @@ public class TcpInfoTest { @Test public void testParseTcpInfo() { final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES); - final Map<TcpInfo.Field, Number> expected = makeTestTcpInfoHash(); + // Length is less than required + final TcpInfo nullInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO); + assertEquals(nullInfo, null); + final TcpInfo parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1); + assertEquals(parsedInfo, TEST_TCPINFO); + + // Make a data that TcpInfo is not started from the begining of the buffer. + final ByteBuffer bufferWithHeader = + ByteBuffer.allocate(EXPANDED_TCP_INFO_BYTES.length + TCP_INFO_BYTES.length); + bufferWithHeader.put(EXPANDED_TCP_INFO_BYTES); + bufferWithHeader.put(TCP_INFO_BYTES); + final TcpInfo infoWithHeader = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1); + bufferWithHeader.position(EXPANDED_TCP_INFO_BYTES.length); + assertEquals(parsedInfo, TEST_TCPINFO); + } - assertEquals(parsedInfo, new TcpInfo(expected)); + @Test + public void testFieldOffset() { + assertEquals(TcpInfo.RETRANSMITS_OFFSET, 2); + assertEquals(TcpInfo.LOST_OFFSET, 32); + assertEquals(TcpInfo.SEGS_OUT_OFFSET, 136); + assertEquals(TcpInfo.SEGS_IN_OFFSET, 140); } @Test public void testParseTcpInfoExpanded() { final ByteBuffer buffer = ByteBuffer.wrap(EXPANDED_TCP_INFO_BYTES); - final Map<TcpInfo.Field, Number> expected = makeTestTcpInfoHash(); final TcpInfo parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1 + EXPANDED_TCP_INFO_LENGTH); - assertEquals(parsedInfo, new TcpInfo(expected)); + assertEquals(parsedInfo, TEST_TCPINFO); assertEquals(buffer.limit(), buffer.position()); // reset the index. buffer.position(0); final TcpInfo parsedInfoShorterLen = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1); - assertEquals(parsedInfoShorterLen, new TcpInfo(expected)); + assertEquals(parsedInfoShorterLen, TEST_TCPINFO); assertEquals(TCP_INFO_LENGTH_V1, buffer.position()); } @Test - public void testValidOffset() { - final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES); - - final Map<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); @@ -156,39 +166,14 @@ public class TcpInfoTest { @Test public void testMalformedTcpInfo() { final ByteBuffer buffer = ByteBuffer.wrap(MALFORMED_TCP_INFO_BYTES); - final Map<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash(); TcpInfo parsedInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO); - assertEquals(parsedInfo, new TcpInfo(expected)); + assertEquals(parsedInfo, null); parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1); assertEquals(parsedInfo, null); } - @Test - public void testGetValue() { - ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES); - - final Map<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 Map<TcpInfo.Field, Number> makeShortTestTcpInfoHash() { final Map<TcpInfo.Field, Number> info = new LinkedHashMap<>(); diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java index a21e7cf..6a09f12 100644 --- a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java +++ b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java @@ -61,7 +61,6 @@ import org.mockito.quality.Strictness; import java.io.FileDescriptor; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.HashMap; // TODO: Add more tests for missing coverage. @RunWith(AndroidJUnit4.class) @@ -174,6 +173,8 @@ public class TcpSocketTrackerTest { "0000000000000000"; // deliverRate = 0 private static final byte[] SOCK_DIAG_TCP_INET_BYTES = HexEncoding.decode(SOCK_DIAG_TCP_INET_HEX.toCharArray(), false); + private static final TcpInfo TEST_TCPINFO = + new TcpInfo(5 /* retransmits */, 0 /* lost */, 10 /* segsOut */, 0 /* segsIn */); private static final String TEST_RESPONSE_HEX = SOCK_DIAG_TCP_INET_HEX // struct nlmsghdr @@ -253,52 +254,8 @@ public class TcpSocketTrackerTest { buffer.position(SOCKDIAG_MSG_HEADER_SIZE); final TcpSocketTracker.SocketInfo parsed = tst.parseSockInfo(buffer, AF_INET, 276, 100L); - final HashMap<TcpInfo.Field, Number> expected = new HashMap<>(); - expected.put(TcpInfo.Field.STATE, (byte) 0x01); - expected.put(TcpInfo.Field.CASTATE, (byte) 0x00); - expected.put(TcpInfo.Field.RETRANSMITS, (byte) 0x05); - expected.put(TcpInfo.Field.PROBES, (byte) 0x00); - expected.put(TcpInfo.Field.BACKOFF, (byte) 0x00); - expected.put(TcpInfo.Field.OPTIONS, (byte) 0x07); - expected.put(TcpInfo.Field.WSCALE, (byte) 0x88); - expected.put(TcpInfo.Field.DELIVERY_RATE_APP_LIMITED, (byte) 0x00); - expected.put(TcpInfo.Field.RTO, 1806666); - expected.put(TcpInfo.Field.ATO, 0); - expected.put(TcpInfo.Field.SND_MSS, 1326); - expected.put(TcpInfo.Field.RCV_MSS, 536); - expected.put(TcpInfo.Field.UNACKED, 0); - expected.put(TcpInfo.Field.SACKED, 0); - expected.put(TcpInfo.Field.LOST, 0); - expected.put(TcpInfo.Field.RETRANS, 0); - expected.put(TcpInfo.Field.FACKETS, 0); - expected.put(TcpInfo.Field.LAST_DATA_SENT, 187); - expected.put(TcpInfo.Field.LAST_ACK_SENT, 0); - expected.put(TcpInfo.Field.LAST_DATA_RECV, 187); - expected.put(TcpInfo.Field.LAST_ACK_RECV, 187); - expected.put(TcpInfo.Field.PMTU, 1500); - expected.put(TcpInfo.Field.RCV_SSTHRESH, 87600); - expected.put(TcpInfo.Field.RTT, 601150); - expected.put(TcpInfo.Field.RTTVAR, 300575); - expected.put(TcpInfo.Field.SND_SSTHRESH, 1400); - expected.put(TcpInfo.Field.SND_CWND, 10); - expected.put(TcpInfo.Field.ADVMSS, 1448); - expected.put(TcpInfo.Field.REORDERING, 3); - expected.put(TcpInfo.Field.RCV_RTT, 0); - expected.put(TcpInfo.Field.RCV_SPACE, 87600); - expected.put(TcpInfo.Field.TOTAL_RETRANS, 0); - expected.put(TcpInfo.Field.PACING_RATE, 44115L); - expected.put(TcpInfo.Field.MAX_PACING_RATE, -1L); - expected.put(TcpInfo.Field.BYTES_ACKED, 1L); - expected.put(TcpInfo.Field.BYTES_RECEIVED, 0L); - expected.put(TcpInfo.Field.SEGS_OUT, 10); - expected.put(TcpInfo.Field.SEGS_IN, 0); - expected.put(TcpInfo.Field.NOTSENT_BYTES, 0); - expected.put(TcpInfo.Field.MIN_RTT, 601150); - expected.put(TcpInfo.Field.DATA_SEGS_IN, 0); - expected.put(TcpInfo.Field.DATA_SEGS_OUT, 0); - expected.put(TcpInfo.Field.DELIVERY_RATE, 0L); - - assertEquals(parsed.tcpInfo, new TcpInfo(expected)); + + assertEquals(parsed.tcpInfo, TEST_TCPINFO); assertEquals(parsed.fwmark, 789125); assertEquals(parsed.updateTime, 100); assertEquals(parsed.ipFamily, AF_INET); |