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