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 import androidx.annotation.VisibleForTesting;
23 
24 import java.nio.BufferOverflowException;
25 import java.nio.BufferUnderflowException;
26 import java.nio.ByteBuffer;
27 import java.util.Objects;
28 
29 /**
30  * Class for tcp_info.
31  *
32  * Corresponds to {@code struct tcp_info} from bionic/libc/kernel/uapi/linux/tcp.h
33  */
34 public class TcpInfo {
35     public enum Field {
36         STATE(Byte.BYTES),
37         CASTATE(Byte.BYTES),
38         RETRANSMITS(Byte.BYTES),
39         PROBES(Byte.BYTES),
40         BACKOFF(Byte.BYTES),
41         OPTIONS(Byte.BYTES),
42         WSCALE(Byte.BYTES),
43         DELIVERY_RATE_APP_LIMITED(Byte.BYTES),
44         RTO(Integer.BYTES),
45         ATO(Integer.BYTES),
46         SND_MSS(Integer.BYTES),
47         RCV_MSS(Integer.BYTES),
48         UNACKED(Integer.BYTES),
49         SACKED(Integer.BYTES),
50         LOST(Integer.BYTES),
51         RETRANS(Integer.BYTES),
52         FACKETS(Integer.BYTES),
53         LAST_DATA_SENT(Integer.BYTES),
54         LAST_ACK_SENT(Integer.BYTES),
55         LAST_DATA_RECV(Integer.BYTES),
56         LAST_ACK_RECV(Integer.BYTES),
57         PMTU(Integer.BYTES),
58         RCV_SSTHRESH(Integer.BYTES),
59         RTT(Integer.BYTES),
60         RTTVAR(Integer.BYTES),
61         SND_SSTHRESH(Integer.BYTES),
62         SND_CWND(Integer.BYTES),
63         ADVMSS(Integer.BYTES),
64         REORDERING(Integer.BYTES),
65         RCV_RTT(Integer.BYTES),
66         RCV_SPACE(Integer.BYTES),
67         TOTAL_RETRANS(Integer.BYTES),
68         PACING_RATE(Long.BYTES),
69         MAX_PACING_RATE(Long.BYTES),
70         BYTES_ACKED(Long.BYTES),
71         BYTES_RECEIVED(Long.BYTES),
72         SEGS_OUT(Integer.BYTES),
73         SEGS_IN(Integer.BYTES),
74         NOTSENT_BYTES(Integer.BYTES),
75         MIN_RTT(Integer.BYTES),
76         DATA_SEGS_IN(Integer.BYTES),
77         DATA_SEGS_OUT(Integer.BYTES),
78         DELIVERY_RATE(Long.BYTES),
79         BUSY_TIME(Long.BYTES),
80         RWND_LIMITED(Long.BYTES),
81         SNDBUF_LIMITED(Long.BYTES);
82 
83         public final int size;
84 
Field(int s)85         Field(int s) {
86             size = s;
87         }
88     }
89 
90     private static final String TAG = "TcpInfo";
91     @VisibleForTesting
92     static final int LOST_OFFSET = getFieldOffset(Field.LOST);
93     @VisibleForTesting
94     static final int RETRANSMITS_OFFSET = getFieldOffset(Field.RETRANSMITS);
95     @VisibleForTesting
96     static final int SEGS_IN_OFFSET = getFieldOffset(Field.SEGS_IN);
97     @VisibleForTesting
98     static final int SEGS_OUT_OFFSET = getFieldOffset(Field.SEGS_OUT);
99     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
100     static final int TOTAL_RETRANS_OFFSET = getFieldOffset(Field.TOTAL_RETRANS);
101     /**
102      * This counts individual incoming packets that appeared on the wire, including:
103      * SYN, SYN-ACK, pure ACKs, data segments (after segmentation offload into small <=mtu
104      * packets), FIN, FIN-ACK, and any retransmits.
105      *
106      * This field is read from the tcpi_segs_in field from {@code struct tcp_info}
107      * in bionic/libc/kernel/uapi/linux/tcp.h. Also see [tcpEStatsPerfSegsIn] in the RFC4898.
108      */
109     final int mSegsIn;
110     /**
111      * This counts individual outgoing packets that have been sent to the network, including:
112      * SYN, SYN-ACK, pure ACKs, data segments (after segmentation offload into small <=mtu
113      * packets), FIN, FIN-ACK, and any retransmits.
114      *
115      * This field is read from the tcpi_segs_out field from {@code struct tcp_info}
116      * in bionic/libc/kernel/uapi/linux/tcp.h. Also see [tcpEStatsPerfSegsOut] in the RFC4898.
117      */
118     final int mSegsOut;
119     /**
120      * This counts individual accumulated retransmitted packets that have been sent to the network,
121      * including any retransmits for SYN, SYN-ACK, pure ACKs, data segments (after segmentation
122      * offload into small <=mtu packets), FIN and FIN-ACK.
123      *
124      * This field is read from the tcpi_total_retrans field from {@code struct tcp_info}
125      * in bionic/libc/kernel/uapi/linux/tcp.h.
126      */
127     final int mTotalRetrans;
128 
getFieldOffset(@onNull final Field needle)129     private static int getFieldOffset(@NonNull final Field needle) {
130         int offset = 0;
131         for (final Field field : Field.values()) {
132             if (field == needle) return offset;
133             offset += field.size;
134         }
135         throw new IllegalArgumentException("Unknown field");
136     }
137 
TcpInfo(@onNull ByteBuffer bytes, int infolen)138     private TcpInfo(@NonNull ByteBuffer bytes, int infolen) {
139         // SEGS_IN is the last required field in the buffer, so if the buffer is long enough for
140         // SEGS_IN it's long enough for everything
141         if (SEGS_IN_OFFSET + Field.SEGS_IN.size > infolen) {
142             throw new IllegalArgumentException("Length " + infolen + " is less than required.");
143         }
144         final int start = bytes.position();
145         mSegsIn = bytes.getInt(start + SEGS_IN_OFFSET);
146         mSegsOut = bytes.getInt(start + SEGS_OUT_OFFSET);
147         mTotalRetrans = bytes.get(start + TOTAL_RETRANS_OFFSET);
148         // tcp_info structure grows over time as new fields are added. Jump to the end of the
149         // structure, as unknown fields might remain at the end of the structure if the tcp_info
150         // struct was expanded.
151         bytes.position(Math.min(infolen + start, bytes.limit()));
152     }
153 
154     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
TcpInfo(int segsOut, int segsIn, int totalRetrans)155     TcpInfo(int segsOut, int segsIn, int totalRetrans) {
156         mSegsOut = segsOut;
157         mSegsIn = segsIn;
158         mTotalRetrans = totalRetrans;
159     }
160 
161     /** Parse a TcpInfo from a giving ByteBuffer with a specific length. */
162     @Nullable
parse(@onNull ByteBuffer bytes, int infolen)163     public static TcpInfo parse(@NonNull ByteBuffer bytes, int infolen) {
164         try {
165             return new TcpInfo(bytes, infolen);
166         } catch (BufferUnderflowException | BufferOverflowException | IllegalArgumentException
167                 | IndexOutOfBoundsException e) {
168             Log.e(TAG, "parsing error.", e);
169             return null;
170         }
171     }
172 
173     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
decodeWscale(byte num)174     static String decodeWscale(byte num) {
175         return String.valueOf((num >> 4) & 0x0f)  + ":" + String.valueOf(num & 0x0f);
176     }
177 
178     /**
179      *  Returns a string representing a given tcp state.
180      *  Map to enum in bionic/libc/include/netinet/tcp.h
181      */
182     @VisibleForTesting
getTcpStateName(int state)183     static String getTcpStateName(int state) {
184         switch (state) {
185             case 1: return "TCP_ESTABLISHED";
186             case 2: return "TCP_SYN_SENT";
187             case 3: return "TCP_SYN_RECV";
188             case 4: return "TCP_FIN_WAIT1";
189             case 5: return "TCP_FIN_WAIT2";
190             case 6: return "TCP_TIME_WAIT";
191             case 7: return "TCP_CLOSE";
192             case 8: return "TCP_CLOSE_WAIT";
193             case 9: return "TCP_LAST_ACK";
194             case 10: return "TCP_LISTEN";
195             case 11: return "TCP_CLOSING";
196             default: return "UNKNOWN:" + Integer.toString(state);
197         }
198     }
199 
200     @Override
equals(Object obj)201     public boolean equals(Object obj) {
202         if (!(obj instanceof TcpInfo)) return false;
203         TcpInfo other = (TcpInfo) obj;
204 
205         return mSegsIn == other.mSegsIn && mSegsOut == other.mSegsOut
206                 && mTotalRetrans == other.mTotalRetrans;
207     }
208 
209     @Override
hashCode()210     public int hashCode() {
211         return Objects.hash(mSegsIn, mSegsOut, mTotalRetrans);
212     }
213 
214     @Override
toString()215     public String toString() {
216         return "TcpInfo{received=" + mSegsIn + ", sent=" + mSegsOut
217                 + ", totalRetrans=" + mTotalRetrans + "}";
218     }
219 }
220