1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.networkstack.netlink; 17 18 import android.util.Log; 19 20 import androidx.annotation.NonNull; 21 import androidx.annotation.Nullable; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 25 import java.nio.BufferOverflowException; 26 import java.nio.BufferUnderflowException; 27 import java.nio.ByteBuffer; 28 import java.util.Objects; 29 30 /** 31 * Class for tcp_info. 32 * 33 * Corresponds to {@code struct tcp_info} from bionic/libc/kernel/uapi/linux/tcp.h 34 */ 35 public class TcpInfo { 36 public enum Field { 37 STATE(Byte.BYTES), 38 CASTATE(Byte.BYTES), 39 RETRANSMITS(Byte.BYTES), 40 PROBES(Byte.BYTES), 41 BACKOFF(Byte.BYTES), 42 OPTIONS(Byte.BYTES), 43 WSCALE(Byte.BYTES), 44 DELIVERY_RATE_APP_LIMITED(Byte.BYTES), 45 RTO(Integer.BYTES), 46 ATO(Integer.BYTES), 47 SND_MSS(Integer.BYTES), 48 RCV_MSS(Integer.BYTES), 49 UNACKED(Integer.BYTES), 50 SACKED(Integer.BYTES), 51 LOST(Integer.BYTES), 52 RETRANS(Integer.BYTES), 53 FACKETS(Integer.BYTES), 54 LAST_DATA_SENT(Integer.BYTES), 55 LAST_ACK_SENT(Integer.BYTES), 56 LAST_DATA_RECV(Integer.BYTES), 57 LAST_ACK_RECV(Integer.BYTES), 58 PMTU(Integer.BYTES), 59 RCV_SSTHRESH(Integer.BYTES), 60 RTT(Integer.BYTES), 61 RTTVAR(Integer.BYTES), 62 SND_SSTHRESH(Integer.BYTES), 63 SND_CWND(Integer.BYTES), 64 ADVMSS(Integer.BYTES), 65 REORDERING(Integer.BYTES), 66 RCV_RTT(Integer.BYTES), 67 RCV_SPACE(Integer.BYTES), 68 TOTAL_RETRANS(Integer.BYTES), 69 PACING_RATE(Long.BYTES), 70 MAX_PACING_RATE(Long.BYTES), 71 BYTES_ACKED(Long.BYTES), 72 BYTES_RECEIVED(Long.BYTES), 73 SEGS_OUT(Integer.BYTES), 74 SEGS_IN(Integer.BYTES), 75 NOTSENT_BYTES(Integer.BYTES), 76 MIN_RTT(Integer.BYTES), 77 DATA_SEGS_IN(Integer.BYTES), 78 DATA_SEGS_OUT(Integer.BYTES), 79 DELIVERY_RATE(Long.BYTES), 80 BUSY_TIME(Long.BYTES), 81 RWND_LIMITED(Long.BYTES), 82 SNDBUF_LIMITED(Long.BYTES); 83 84 public final int size; 85 Field(int s)86 Field(int s) { 87 size = s; 88 } 89 } 90 91 private static final String TAG = "TcpInfo"; 92 @VisibleForTesting 93 static final int LOST_OFFSET = getFieldOffset(Field.LOST); 94 @VisibleForTesting 95 static final int RETRANSMITS_OFFSET = getFieldOffset(Field.RETRANSMITS); 96 @VisibleForTesting 97 static final int SEGS_IN_OFFSET = getFieldOffset(Field.SEGS_IN); 98 @VisibleForTesting 99 static final int SEGS_OUT_OFFSET = getFieldOffset(Field.SEGS_OUT); 100 final int mSegsIn; 101 final int mSegsOut; 102 final int mLost; 103 final int mRetransmits; 104 getFieldOffset(@onNull final Field needle)105 private static int getFieldOffset(@NonNull final Field needle) { 106 int offset = 0; 107 for (final Field field : Field.values()) { 108 if (field == needle) return offset; 109 offset += field.size; 110 } 111 throw new IllegalArgumentException("Unknown field"); 112 } 113 TcpInfo(@onNull ByteBuffer bytes, int infolen)114 private TcpInfo(@NonNull ByteBuffer bytes, int infolen) { 115 // SEGS_IN is the last required field in the buffer, so if the buffer is long enough for 116 // SEGS_IN it's long enough for everything 117 if (SEGS_IN_OFFSET + Field.SEGS_IN.size > infolen) { 118 throw new IllegalArgumentException("Length " + infolen + " is less than required."); 119 } 120 final int start = bytes.position(); 121 mSegsIn = bytes.getInt(start + SEGS_IN_OFFSET); 122 mSegsOut = bytes.getInt(start + SEGS_OUT_OFFSET); 123 mLost = bytes.getInt(start + LOST_OFFSET); 124 mRetransmits = bytes.get(start + RETRANSMITS_OFFSET); 125 // tcp_info structure grows over time as new fields are added. Jump to the end of the 126 // structure, as unknown fields might remain at the end of the structure if the tcp_info 127 // struct was expanded. 128 bytes.position(Math.min(infolen + start, bytes.limit())); 129 } 130 131 @VisibleForTesting TcpInfo(int retransmits, int lost, int segsOut, int segsIn)132 TcpInfo(int retransmits, int lost, int segsOut, int segsIn) { 133 mRetransmits = retransmits; 134 mLost = lost; 135 mSegsOut = segsOut; 136 mSegsIn = segsIn; 137 } 138 139 /** Parse a TcpInfo from a giving ByteBuffer with a specific length. */ 140 @Nullable parse(@onNull ByteBuffer bytes, int infolen)141 public static TcpInfo parse(@NonNull ByteBuffer bytes, int infolen) { 142 try { 143 return new TcpInfo(bytes, infolen); 144 } catch (BufferUnderflowException | BufferOverflowException | IllegalArgumentException 145 | IndexOutOfBoundsException e) { 146 Log.e(TAG, "parsing error.", e); 147 return null; 148 } 149 } 150 decodeWscale(byte num)151 private static String decodeWscale(byte num) { 152 return String.valueOf((num >> 4) & 0x0f) + ":" + String.valueOf(num & 0x0f); 153 } 154 155 /** 156 * Returns a string representing a given tcp state. 157 * Map to enum in bionic/libc/include/netinet/tcp.h 158 */ 159 @VisibleForTesting getTcpStateName(int state)160 static String getTcpStateName(int state) { 161 switch (state) { 162 case 1: return "TCP_ESTABLISHED"; 163 case 2: return "TCP_SYN_SENT"; 164 case 3: return "TCP_SYN_RECV"; 165 case 4: return "TCP_FIN_WAIT1"; 166 case 5: return "TCP_FIN_WAIT2"; 167 case 6: return "TCP_TIME_WAIT"; 168 case 7: return "TCP_CLOSE"; 169 case 8: return "TCP_CLOSE_WAIT"; 170 case 9: return "TCP_LAST_ACK"; 171 case 10: return "TCP_LISTEN"; 172 case 11: return "TCP_CLOSING"; 173 default: return "UNKNOWN:" + Integer.toString(state); 174 } 175 } 176 177 @Override equals(Object obj)178 public boolean equals(Object obj) { 179 if (!(obj instanceof TcpInfo)) return false; 180 TcpInfo other = (TcpInfo) obj; 181 182 return mSegsIn == other.mSegsIn && mSegsOut == other.mSegsOut 183 && mRetransmits == other.mRetransmits && mLost == other.mLost; 184 } 185 186 @Override hashCode()187 public int hashCode() { 188 return Objects.hash(mLost, mRetransmits, mSegsIn, mSegsOut); 189 } 190 191 @Override toString()192 public String toString() { 193 return "TcpInfo{lost=" + mLost + ", retransmit=" + mRetransmits + ", received=" + mSegsIn 194 + ", sent=" + mSegsOut + "}"; 195 } 196 } 197