/* * 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 androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; 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"; @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); @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) static final int TOTAL_RETRANS_OFFSET = getFieldOffset(Field.TOTAL_RETRANS); /** * This counts individual incoming packets that appeared on the wire, including: * SYN, SYN-ACK, pure ACKs, data segments (after segmentation offload into small <=mtu * packets), FIN, FIN-ACK, and any retransmits. * * This field is read from the tcpi_segs_in field from {@code struct tcp_info} * in bionic/libc/kernel/uapi/linux/tcp.h. Also see [tcpEStatsPerfSegsIn] in the RFC4898. */ final int mSegsIn; /** * This counts individual outgoing packets that have been sent to the network, including: * SYN, SYN-ACK, pure ACKs, data segments (after segmentation offload into small <=mtu * packets), FIN, FIN-ACK, and any retransmits. * * This field is read from the tcpi_segs_out field from {@code struct tcp_info} * in bionic/libc/kernel/uapi/linux/tcp.h. Also see [tcpEStatsPerfSegsOut] in the RFC4898. */ final int mSegsOut; /** * This counts individual accumulated retransmitted packets that have been sent to the network, * including any retransmits for SYN, SYN-ACK, pure ACKs, data segments (after segmentation * offload into small <=mtu packets), FIN and FIN-ACK. * * This field is read from the tcpi_total_retrans field from {@code struct tcp_info} * in bionic/libc/kernel/uapi/linux/tcp.h. */ final int mTotalRetrans; 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) { // 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."); } final int start = bytes.position(); mSegsIn = bytes.getInt(start + SEGS_IN_OFFSET); mSegsOut = bytes.getInt(start + SEGS_OUT_OFFSET); mTotalRetrans = bytes.get(start + TOTAL_RETRANS_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. bytes.position(Math.min(infolen + start, bytes.limit())); } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) TcpInfo(int segsOut, int segsIn, int totalRetrans) { mSegsOut = segsOut; mSegsIn = segsIn; mTotalRetrans = totalRetrans; } /** Parse a TcpInfo from a giving ByteBuffer with a specific length. */ @Nullable public static TcpInfo parse(@NonNull ByteBuffer bytes, int infolen) { try { return new TcpInfo(bytes, infolen); } catch (BufferUnderflowException | BufferOverflowException | IllegalArgumentException | IndexOutOfBoundsException e) { Log.e(TAG, "parsing error.", e); return null; } } @VisibleForTesting(otherwise = VisibleForTesting.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 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; return mSegsIn == other.mSegsIn && mSegsOut == other.mSegsOut && mTotalRetrans == other.mTotalRetrans; } @Override public int hashCode() { return Objects.hash(mSegsIn, mSegsOut, mTotalRetrans); } @Override public String toString() { return "TcpInfo{received=" + mSegsIn + ", sent=" + mSegsOut + ", totalRetrans=" + mTotalRetrans + "}"; } }