xref: /illumos-gate/usr/src/cmd/connstat/connstat_tcp.c (revision d8849d7dee03b84a3fa281ec65eb9e3d86d3756b)
1 /*
2  * CDDL HEADER START
3  *
4  * This file and its contents are supplied under the terms of the
5  * Common Development and Distribution License ("CDDL"), version 1.0.
6  * You may only use this file in accordance with the terms of version
7  * 1.0 of the CDDL.
8  *
9  * A full copy of the text of the CDDL should have accompanied this
10  * source.  A copy of the CDDL is also available via the Internet at
11  * http://www.illumos.org/license/CDDL.
12  *
13  * CDDL HEADER END
14  */
15 /*
16  * Copyright (c) 2015, 2016 by Delphix. All rights reserved.
17  */
18 
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <inet/mib2.h>
23 #include <sys/debug.h>
24 #include <sys/stropts.h>
25 #include <sys/types.h>
26 #include <sys/socket.h>
27 #include <netinet/in.h>
28 #include <inet/tcp.h>
29 #include <arpa/inet.h>
30 #include <ofmt.h>
31 #include <sys/time.h>
32 #include "connstat_mib.h"
33 #include "connstat_tcp.h"
34 
35 /*
36  * The byte order of some of the fields in this code can be a bit confusing.
37  * When using sockaddr_in(6) structs, the address and ports are always in
38  * Network Byte Order (Big Endian), as required by sockaddr(3SOCKET).
39  *
40  * When using the structs mib2_tcpConnEntry_t and mib2_tcp6ConnEntry_t, the
41  * address fields (tcp(6)ConnLocalAddress and tcp(6)ConnRemAdddress) are in
42  * Network Byte Order. Note, however, that the port fields ARE NOT, but are
43  * instead in Host Byte Order. This isn't a problem though, since the ports
44  * we filter on from the command-line (ca_lport and ca_rport) are kept in
45  * Host Byte Order after parsing.
46  *
47  * Since the t_lport and t_rport fields come from the MIB structs, they are
48  * likewise stored in Host Byte Order (and need to be for printing). The
49  * t_laddr and t_raddr fields are string representations of the addresses,
50  * so they don't require any special attention.
51  *
52  * All of the statistics (such as bytes read and written, current window
53  * sizes, etc.) are in Host Byte Order.
54  */
55 
56 typedef struct tcp_fields_buf_s {
57 	char t_laddr[INET6_ADDRSTRLEN];
58 	char t_raddr[INET6_ADDRSTRLEN];
59 	uint16_t t_lport;
60 	uint16_t t_rport;
61 	uint64_t t_inbytes;
62 	uint64_t t_insegs;
63 	uint64_t t_inunorderbytes;
64 	uint64_t t_inunordersegs;
65 	uint64_t t_outbytes;
66 	uint64_t t_outsegs;
67 	uint64_t t_retransbytes;
68 	uint64_t t_retranssegs;
69 	uint32_t t_suna;
70 	uint32_t t_unsent;
71 	uint32_t t_swnd;
72 	uint32_t t_cwnd;
73 	uint32_t t_rwnd;
74 	uint32_t t_mss;
75 	uint32_t t_rto;
76 	uint32_t t_rtt_cnt;
77 	uint64_t t_rtt_sum;
78 	int t_state;
79 	uint64_t t_rtt;
80 } tcp_fields_buf_t;
81 
82 static boolean_t print_tcp_state(ofmt_arg_t *, char *, uint_t);
83 
84 static ofmt_field_t tcp_fields[] = {
85 	{ "LADDR",	26,
86 		offsetof(tcp_fields_buf_t, t_laddr),	print_string },
87 	{ "RADDR",	26,
88 		offsetof(tcp_fields_buf_t, t_raddr),	print_string },
89 	{ "LPORT",	6,
90 		offsetof(tcp_fields_buf_t, t_lport),	print_uint16 },
91 	{ "RPORT",	6,
92 		offsetof(tcp_fields_buf_t, t_rport),	print_uint16 },
93 	{ "INBYTES",	11,
94 		offsetof(tcp_fields_buf_t, t_inbytes),	print_uint64 },
95 	{ "INSEGS",	11,
96 		offsetof(tcp_fields_buf_t, t_insegs),	print_uint64 },
97 	{ "INUNORDERBYTES",	15,
98 		offsetof(tcp_fields_buf_t, t_inunorderbytes),	print_uint64 },
99 	{ "INUNORDERSEGS",	14,
100 		offsetof(tcp_fields_buf_t, t_inunordersegs),	print_uint64 },
101 	{ "OUTBYTES",	11,
102 		offsetof(tcp_fields_buf_t, t_outbytes),	print_uint64 },
103 	{ "OUTSEGS",	11,
104 		offsetof(tcp_fields_buf_t, t_outsegs),	print_uint64 },
105 	{ "RETRANSBYTES",	13,
106 		offsetof(tcp_fields_buf_t, t_retransbytes),	print_uint64 },
107 	{ "RETRANSSEGS",	12,
108 		offsetof(tcp_fields_buf_t, t_retranssegs),	print_uint64 },
109 	{ "SUNA",	11,
110 		offsetof(tcp_fields_buf_t, t_suna),	print_uint32 },
111 	{ "UNSENT",	11,
112 		offsetof(tcp_fields_buf_t, t_unsent),	print_uint32 },
113 	{ "SWND",	11,
114 		offsetof(tcp_fields_buf_t, t_swnd),	print_uint32 },
115 	{ "CWND",	11,
116 		offsetof(tcp_fields_buf_t, t_cwnd),	print_uint32 },
117 	{ "RWND",	11,
118 		offsetof(tcp_fields_buf_t, t_rwnd),	print_uint32 },
119 	{ "MSS",	6,
120 		offsetof(tcp_fields_buf_t, t_mss),	print_uint32 },
121 	{ "RTO",	8,
122 		offsetof(tcp_fields_buf_t, t_rto),	print_uint32 },
123 	{ "RTT",	8,
124 		offsetof(tcp_fields_buf_t, t_rtt),	print_uint64 },
125 	{ "RTTS",	8,
126 		offsetof(tcp_fields_buf_t, t_rtt_sum),	print_uint64 },
127 	{ "RTTC",	11,
128 		offsetof(tcp_fields_buf_t, t_rtt_cnt),	print_uint32 },
129 	{ "STATE",	12,
130 		offsetof(tcp_fields_buf_t, t_state),	print_tcp_state },
131 	{ NULL, 0, 0, NULL}
132 };
133 
134 static tcp_fields_buf_t fields_buf;
135 
136 
137 typedef struct tcp_state_info_s {
138 	int tsi_state;
139 	const char *tsi_string;
140 } tcp_state_info_t;
141 
142 tcp_state_info_t tcp_state_info[] = {
143 	{ TCPS_CLOSED, "CLOSED" },
144 	{ TCPS_IDLE, "IDLE" },
145 	{ TCPS_BOUND, "BOUND" },
146 	{ TCPS_LISTEN, "LISTEN" },
147 	{ TCPS_SYN_SENT, "SYN_SENT" },
148 	{ TCPS_SYN_RCVD, "SYN_RCVD" },
149 	{ TCPS_ESTABLISHED, "ESTABLISHED" },
150 	{ TCPS_CLOSE_WAIT, "CLOSE_WAIT" },
151 	{ TCPS_FIN_WAIT_1, "FIN_WAIT_1" },
152 	{ TCPS_CLOSING, "CLOSING" },
153 	{ TCPS_LAST_ACK, "LAST_ACK" },
154 	{ TCPS_FIN_WAIT_2, "FIN_WAIT_2" },
155 	{ TCPS_TIME_WAIT, "TIME_WAIT" },
156 	{ TCPS_CLOSED - 1, NULL }
157 };
158 
159 ofmt_field_t *
160 tcp_get_fields(void)
161 {
162 	return (tcp_fields);
163 }
164 
165 /*
166  * Extract information from the connection info structure into the global
167  * output buffer.
168  */
169 static void
170 tcp_ci2buf(struct tcpConnEntryInfo_s *ci)
171 {
172 	fields_buf.t_inbytes =
173 	    ci->ce_in_data_inorder_bytes + ci->ce_in_data_unorder_bytes;
174 	fields_buf.t_insegs =
175 	    ci->ce_in_data_inorder_segs + ci->ce_in_data_unorder_segs;
176 	fields_buf.t_inunorderbytes = ci->ce_in_data_unorder_bytes;
177 	fields_buf.t_inunordersegs = ci->ce_in_data_unorder_segs;
178 	fields_buf.t_outbytes = ci->ce_out_data_bytes;
179 	fields_buf.t_outsegs = ci->ce_out_data_segs;
180 	fields_buf.t_retransbytes = ci->ce_out_retrans_bytes;
181 	fields_buf.t_retranssegs = ci->ce_out_retrans_segs;
182 	fields_buf.t_suna = ci->ce_snxt - ci->ce_suna;
183 	fields_buf.t_unsent = ci->ce_unsent;
184 	fields_buf.t_swnd = ci->ce_swnd;
185 	fields_buf.t_cwnd = ci->ce_cwnd;
186 	fields_buf.t_rwnd = ci->ce_rwnd;
187 	fields_buf.t_mss = ci->ce_mss;
188 	fields_buf.t_rto = ci->ce_rto;
189 	fields_buf.t_rtt = (ci->ce_out_data_segs == 0 ? 0 : ci->ce_rtt_sa);
190 	fields_buf.t_rtt_sum = ci->ce_rtt_sum;
191 	fields_buf.t_rtt_cnt = ci->ce_rtt_cnt;
192 	fields_buf.t_state = ci->ce_state;
193 }
194 
195 /*
196  * Extract information from the connection entry into the global output
197  * buffer.
198  */
199 static void
200 tcp_ipv4_ce2buf(mib2_tcpConnEntry_t *ce)
201 {
202 	VERIFY3P(inet_ntop(AF_INET, (void *)&ce->tcpConnLocalAddress,
203 	    fields_buf.t_laddr, sizeof (fields_buf.t_laddr)), !=, NULL);
204 	VERIFY3P(inet_ntop(AF_INET, (void *)&ce->tcpConnRemAddress,
205 	    fields_buf.t_raddr, sizeof (fields_buf.t_raddr)), !=, NULL);
206 
207 	fields_buf.t_lport = ce->tcpConnLocalPort;
208 	fields_buf.t_rport = ce->tcpConnRemPort;
209 
210 	tcp_ci2buf(&ce->tcpConnEntryInfo);
211 }
212 
213 static void
214 tcp_ipv6_ce2buf(mib2_tcp6ConnEntry_t *ce)
215 {
216 	VERIFY3P(inet_ntop(AF_INET6, (void *)&ce->tcp6ConnLocalAddress,
217 	    fields_buf.t_laddr, sizeof (fields_buf.t_laddr)), !=, NULL);
218 	VERIFY3P(inet_ntop(AF_INET6, (void *)&ce->tcp6ConnRemAddress,
219 	    fields_buf.t_raddr, sizeof (fields_buf.t_raddr)), !=, NULL);
220 
221 	fields_buf.t_lport = ce->tcp6ConnLocalPort;
222 	fields_buf.t_rport = ce->tcp6ConnRemPort;
223 
224 	tcp_ci2buf(&ce->tcp6ConnEntryInfo);
225 }
226 
227 /*
228  * Print a single IPv4 connection entry, taking into account possible
229  * filters that have been set in state.
230  */
231 static void
232 tcp_ipv4_print(mib2_tcpConnEntry_t *ce, conn_walk_state_t *state)
233 {
234 	if (!(state->cws_flags & CS_LOOPBACK) &&
235 	    ntohl(ce->tcpConnLocalAddress) == INADDR_LOOPBACK) {
236 		return;
237 	}
238 
239 	if (state->cws_flags & CS_LADDR) {
240 		struct sockaddr_in *sin =
241 		    (struct sockaddr_in *)&state->cws_filter.ca_laddr;
242 		if (ce->tcpConnLocalAddress != sin->sin_addr.s_addr) {
243 			return;
244 		}
245 	}
246 	if (state->cws_flags & CS_RADDR) {
247 		struct sockaddr_in *sin =
248 		    (struct sockaddr_in *)&state->cws_filter.ca_raddr;
249 		if (ce->tcpConnRemAddress != sin->sin_addr.s_addr) {
250 			return;
251 		}
252 	}
253 	if (state->cws_flags & CS_LPORT) {
254 		if (ce->tcpConnLocalPort != state->cws_filter.ca_lport) {
255 			return;
256 		}
257 	}
258 	if (state->cws_flags & CS_RPORT) {
259 		if (ce->tcpConnRemPort != state->cws_filter.ca_rport) {
260 			return;
261 		}
262 	}
263 
264 	if ((state->cws_flags & CS_STATE) &&
265 	    ce->tcpConnEntryInfo.ce_state != state->cws_filter.ca_state) {
266 		return;
267 	}
268 
269 	tcp_ipv4_ce2buf(ce);
270 	ofmt_print(state->cws_ofmt, &fields_buf);
271 }
272 
273 /*
274  * Print a single IPv6 connection entry, taking into account possible
275  * filters that have been set in state.
276  */
277 static void
278 tcp_ipv6_print(mib2_tcp6ConnEntry_t *ce, conn_walk_state_t *state)
279 {
280 	if (!(state->cws_flags & CS_LOOPBACK) &&
281 	    IN6_IS_ADDR_LOOPBACK(
282 	    (struct in6_addr *)&ce->tcp6ConnLocalAddress)) {
283 		return;
284 	}
285 
286 	if (state->cws_flags & CS_LADDR) {
287 		struct sockaddr_in6 *sin6 =
288 		    (struct sockaddr_in6 *)&state->cws_filter.ca_laddr;
289 		if (!IN6_ARE_ADDR_EQUAL(
290 		    (struct in6_addr *)&ce->tcp6ConnLocalAddress,
291 		    &sin6->sin6_addr)) {
292 			return;
293 		}
294 	}
295 	if (state->cws_flags & CS_RADDR) {
296 		struct sockaddr_in6 *sin6 =
297 		    (struct sockaddr_in6 *)&state->cws_filter.ca_raddr;
298 		if (!IN6_ARE_ADDR_EQUAL(
299 		    (struct in6_addr *)&ce->tcp6ConnRemAddress,
300 		    &sin6->sin6_addr)) {
301 			return;
302 		}
303 	}
304 	if (state->cws_flags & CS_LPORT) {
305 		if (ce->tcp6ConnLocalPort != state->cws_filter.ca_lport) {
306 			return;
307 		}
308 	}
309 	if (state->cws_flags & CS_RPORT) {
310 		if (ce->tcp6ConnRemPort != state->cws_filter.ca_rport) {
311 			return;
312 		}
313 	}
314 
315 	if ((state->cws_flags & CS_STATE) &&
316 	    ce->tcp6ConnEntryInfo.ce_state != state->cws_filter.ca_state) {
317 		return;
318 	}
319 
320 	tcp_ipv6_ce2buf(ce);
321 	ofmt_print(state->cws_ofmt, &fields_buf);
322 }
323 
324 void
325 tcp_walk_ipv4(struct strbuf *dbuf, conn_walk_state_t *state)
326 {
327 	uint_t nconns = (dbuf->len / sizeof (mib2_tcpConnEntry_t));
328 	/* LINTED E_BAD_PTR_CAST_ALIGN */
329 	mib2_tcpConnEntry_t *ce = (mib2_tcpConnEntry_t *)dbuf->buf;
330 
331 	for (; nconns > 0; ce++, nconns--) {
332 		tcp_ipv4_print(ce, state);
333 	}
334 }
335 
336 void
337 tcp_walk_ipv6(struct strbuf *dbuf, conn_walk_state_t *state)
338 {
339 	uint_t nconns = (dbuf->len / sizeof (mib2_tcp6ConnEntry_t));
340 	/* LINTED E_BAD_PTR_CAST_ALIGN */
341 	mib2_tcp6ConnEntry_t *ce = (mib2_tcp6ConnEntry_t *)dbuf->buf;
342 
343 	for (; nconns > 0; ce++, nconns--) {
344 		tcp_ipv6_print(ce, state);
345 	}
346 }
347 
348 static tcp_state_info_t *
349 tcp_stateinfobystate(int state)
350 {
351 	tcp_state_info_t *sip;
352 
353 	for (sip = tcp_state_info; sip->tsi_string != NULL; sip++) {
354 		if (sip->tsi_state == state) {
355 			return (sip);
356 		}
357 	}
358 	return (NULL);
359 }
360 
361 static tcp_state_info_t *
362 tcp_stateinfobystr(const char *statestr)
363 {
364 	tcp_state_info_t *sip;
365 
366 	for (sip = tcp_state_info; sip->tsi_string != NULL; sip++) {
367 		if (strncasecmp(statestr, sip->tsi_string,
368 		    strlen(sip->tsi_string)) == 0) {
369 			return (sip);
370 		}
371 	}
372 	return (NULL);
373 }
374 
375 int
376 tcp_str2state(const char *statestr)
377 {
378 	tcp_state_info_t *sip = tcp_stateinfobystr(statestr);
379 	return (sip == NULL ? TCPS_CLOSED - 1 : sip->tsi_state);
380 }
381 
382 static const char *
383 tcp_state2str(int state)
384 {
385 	tcp_state_info_t *sip = tcp_stateinfobystate(state);
386 	return (sip == NULL ? NULL : sip->tsi_string);
387 }
388 
389 static boolean_t
390 print_tcp_state(ofmt_arg_t *ofarg, char *buf, uint_t bufsize)
391 {
392 	/* LINTED E_BAD_PTR_CAST_ALIGN */
393 	int state = *(int *)((char *)ofarg->ofmt_cbarg + ofarg->ofmt_id);
394 	const char *statestr = tcp_state2str(state);
395 
396 	if (statestr != NULL) {
397 		(void) strlcpy(buf, statestr, bufsize);
398 	} else {
399 		(void) snprintf(buf, bufsize, "UNKNOWN(%d)", state);
400 	}
401 
402 	return (B_TRUE);
403 }
404