1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1980, 1992, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 33 34 /* 35 * netstat 36 */ 37 #include <sys/param.h> 38 #include <sys/queue.h> 39 #include <sys/socket.h> 40 #include <sys/callout.h> 41 #include <sys/socketvar.h> 42 #include <sys/protosw.h> 43 44 #include <netinet/in.h> 45 #include <arpa/inet.h> 46 #include <net/route.h> 47 #include <netinet/in_systm.h> 48 #include <netinet/ip.h> 49 #ifdef INET6 50 #include <netinet/ip6.h> 51 #endif 52 #include <netinet/in_pcb.h> 53 #include <netinet/ip_icmp.h> 54 #include <netinet/icmp_var.h> 55 #include <netinet/ip_var.h> 56 #include <netinet/tcp.h> 57 #include <netinet/tcpip.h> 58 #include <netinet/tcp_seq.h> 59 #define TCPSTATES 60 #include <netinet/tcp_fsm.h> 61 #include <netinet/tcp_var.h> 62 #include <netinet/udp.h> 63 #include <netinet/udp_var.h> 64 65 #include <netdb.h> 66 #include <nlist.h> 67 #include <paths.h> 68 #include <stdlib.h> 69 #include <string.h> 70 71 #include "systat.h" 72 #include "extern.h" 73 74 static void enter(struct xinpcb *, struct xsocket *so, int, const char *); 75 static char *inetname(struct sockaddr *); 76 static void inetprint(struct sockaddr *, const char *); 77 78 #define streq(a,b) (strcmp(a,b)==0) 79 #define YMAX(w) (getmaxy(w)-2) 80 81 WINDOW * 82 opennetstat(void) 83 { 84 sethostent(1); 85 setnetent(1); 86 return (subwin(stdscr, LINES-3-1, 0, MAINWIN_ROW, 0)); 87 } 88 89 struct netinfo { 90 TAILQ_ENTRY(netinfo) chain; 91 short ni_line; /* line on screen */ 92 short ni_seen; /* 0 when not present in list */ 93 short ni_flags; 94 #define NIF_LACHG 0x1 /* local address changed */ 95 #define NIF_FACHG 0x2 /* foreign address changed */ 96 short ni_state; /* tcp state */ 97 const char *ni_proto; /* protocol */ 98 struct sockaddr_storage ni_lsa; /* local address */ 99 struct sockaddr_storage ni_fsa; /* foreign address */ 100 u_int ni_rcvcc; /* rcv buffer character count */ 101 u_int ni_sndcc; /* snd buffer character count */ 102 }; 103 104 static TAILQ_HEAD(netinfohead, netinfo) netcb = TAILQ_HEAD_INITIALIZER(netcb); 105 106 static int aflag = 0; 107 static int nflag = 0; 108 static int lastrow = 1; 109 110 void 111 closenetstat(WINDOW *w) 112 { 113 struct netinfo *p; 114 115 endhostent(); 116 endnetent(); 117 TAILQ_FOREACH(p, &netcb, chain) { 118 if (p->ni_line != -1) 119 lastrow--; 120 p->ni_line = -1; 121 } 122 if (w != NULL) { 123 wclear(w); 124 wrefresh(w); 125 delwin(w); 126 } 127 } 128 129 static const char *miblist[] = { 130 "net.inet.tcp.pcblist", 131 "net.inet.udp.pcblist" 132 }; 133 134 int 135 initnetstat(void) 136 { 137 protos = TCP|UDP; 138 return(1); 139 } 140 141 void 142 fetchnetstat(void) 143 { 144 struct netinfo *p; 145 int idx; 146 struct xinpgen *inpg; 147 char *cur, *end; 148 struct xinpcb *xip = NULL; 149 struct xtcpcb *xtp = NULL; 150 int plen; 151 size_t lsz; 152 153 TAILQ_FOREACH(p, &netcb, chain) 154 p->ni_seen = 0; 155 if (protos&TCP) { 156 idx = 0; 157 } else if (protos&UDP) { 158 idx = 1; 159 } else { 160 error("No protocols to display"); 161 return; 162 } 163 164 for (;idx < 2; idx++) { 165 if (idx == 1 && !(protos&UDP)) 166 break; 167 inpg = (struct xinpgen *)sysctl_dynread(miblist[idx], &lsz); 168 if (inpg == NULL) { 169 error("sysctl(%s...) failed", miblist[idx]); 170 continue; 171 } 172 /* 173 * We currently do no require a consistent pcb list. 174 * Try to be robust in case of struct size changes 175 */ 176 cur = ((char *)inpg) + inpg->xig_len; 177 /* There is also a trailing struct xinpgen */ 178 end = ((char *)inpg) + lsz - inpg->xig_len; 179 if (end <= cur) { 180 free(inpg); 181 continue; 182 } 183 if (idx == 0) { /* TCP */ 184 xtp = (struct xtcpcb *)cur; 185 plen = xtp->xt_len; 186 } else { 187 xip = (struct xinpcb *)cur; 188 plen = xip->xi_len; 189 } 190 while (cur + plen <= end) { 191 if (idx == 0) { /* TCP */ 192 xtp = (struct xtcpcb *)cur; 193 xip = &xtp->xt_inp; 194 } else { 195 xip = (struct xinpcb *)cur; 196 } 197 cur += plen; 198 199 if (!aflag) { 200 if (xip->inp_vflag & INP_IPV4) { 201 if (xip->inp_laddr.s_addr == INADDR_ANY) 202 continue; 203 } 204 #ifdef INET6 205 else if (xip->inp_vflag & INP_IPV6) { 206 if (memcmp(&xip->in6p_laddr, 207 &in6addr_any, sizeof(in6addr_any)) 208 == 0) 209 continue; 210 } 211 #endif 212 } 213 if (nhosts && !checkhost(&xip->inp_inc)) 214 continue; 215 if (nports && !checkport(&xip->inp_inc)) 216 continue; 217 if (idx == 0) 218 enter(xip, &xip->xi_socket, xtp->t_state, 219 "tcp"); 220 else 221 enter(xip, &xip->xi_socket, 0, "udp"); 222 } 223 free(inpg); 224 } 225 } 226 227 static void 228 enter(struct xinpcb *xip, struct xsocket *so, int state, const char *proto) 229 { 230 struct in_conninfo *inc = &xip->inp_inc; 231 uint8_t vflag = xip->inp_vflag; 232 struct netinfo *p; 233 struct sockaddr_storage lsa, fsa; 234 struct sockaddr_in *sa4; 235 #ifdef INET6 236 struct sockaddr_in6 *sa6; 237 #endif 238 239 memset(&lsa, 0, sizeof(lsa)); 240 memset(&fsa, 0, sizeof(fsa)); 241 if (vflag & INP_IPV4) { 242 sa4 = (struct sockaddr_in *)&lsa; 243 sa4->sin_addr = inc->inc_laddr; 244 sa4->sin_port = inc->inc_lport; 245 sa4->sin_family = AF_INET; 246 sa4->sin_len = sizeof(struct sockaddr_in); 247 248 sa4 = (struct sockaddr_in *)&fsa; 249 sa4->sin_addr = inc->inc_faddr; 250 sa4->sin_port = inc->inc_fport; 251 sa4->sin_family = AF_INET; 252 sa4->sin_len = sizeof(struct sockaddr_in); 253 } 254 #ifdef INET6 255 else if (vflag & INP_IPV6) { 256 sa6 = (struct sockaddr_in6 *)&lsa; 257 memcpy(&sa6->sin6_addr, &inc->inc6_laddr, 258 sizeof(struct in6_addr)); 259 sa6->sin6_port = inc->inc_lport; 260 sa6->sin6_family = AF_INET6; 261 sa6->sin6_len = sizeof(struct sockaddr_in6); 262 263 sa6 = (struct sockaddr_in6 *)&fsa; 264 memcpy(&sa6->sin6_addr, &inc->inc6_faddr, 265 sizeof(struct in6_addr)); 266 sa6->sin6_port = inc->inc_fport; 267 sa6->sin6_family = AF_INET6; 268 sa6->sin6_len = sizeof(struct sockaddr_in6); 269 } 270 #endif 271 else 272 return; 273 274 /* 275 * Only take exact matches, any sockets with 276 * previously unbound addresses will be deleted 277 * below in the display routine because they 278 * will appear as ``not seen'' in the kernel 279 * data structures. 280 */ 281 TAILQ_FOREACH(p, &netcb, chain) { 282 if (!streq(proto, p->ni_proto)) 283 continue; 284 if (p->ni_lsa.ss_family != lsa.ss_family || 285 memcmp(&p->ni_lsa, &lsa, lsa.ss_len) != 0) 286 continue; 287 if (p->ni_fsa.ss_family == fsa.ss_family && 288 memcmp(&p->ni_fsa, &fsa, fsa.ss_len) == 0) 289 break; 290 } 291 if (p == NULL) { 292 if ((p = malloc(sizeof(*p))) == NULL) { 293 error("Out of memory"); 294 return; 295 } 296 TAILQ_INSERT_HEAD(&netcb, p, chain); 297 p->ni_line = -1; 298 memcpy(&p->ni_lsa, &lsa, lsa.ss_len); 299 memcpy(&p->ni_fsa, &fsa, fsa.ss_len); 300 p->ni_proto = strdup(proto); 301 p->ni_flags = NIF_LACHG|NIF_FACHG; 302 } 303 p->ni_state = state; 304 p->ni_seen = 1; 305 p->ni_rcvcc = so->so_rcv.sb_cc; 306 p->ni_sndcc = so->so_snd.sb_cc; 307 } 308 309 /* column locations */ 310 #define LADDR 0 311 #define FADDR LADDR+23 312 #define PROTO FADDR+23 313 #define RCVCC PROTO+6 314 #define SNDCC RCVCC+7 315 #define STATE SNDCC+7 316 317 void 318 labelnetstat(void) 319 { 320 wmove(wnd, 0, 0); wclrtobot(wnd); 321 mvwaddstr(wnd, 0, LADDR, "Local Address"); 322 mvwaddstr(wnd, 0, FADDR, "Foreign Address"); 323 mvwaddstr(wnd, 0, PROTO, "Proto"); 324 mvwaddstr(wnd, 0, RCVCC, "Recv-Q"); 325 mvwaddstr(wnd, 0, SNDCC, "Send-Q"); 326 mvwaddstr(wnd, 0, STATE, "(state)"); 327 } 328 329 void 330 shownetstat(void) 331 { 332 struct netinfo *p, *q; 333 char proto[6]; 334 const char *family = ""; 335 336 /* 337 * First, delete any connections that have gone 338 * away and adjust the position of connections 339 * below to reflect the deleted line. 340 */ 341 p = TAILQ_FIRST(&netcb); 342 while (p != NULL) { 343 if (p->ni_line == -1 || p->ni_seen) { 344 p = TAILQ_NEXT(p, chain); 345 continue; 346 } 347 wmove(wnd, p->ni_line, 0); wdeleteln(wnd); 348 TAILQ_FOREACH(q, &netcb, chain) 349 if (q != p && q->ni_line > p->ni_line) { 350 q->ni_line--; 351 /* this shouldn't be necessary */ 352 q->ni_flags |= NIF_LACHG|NIF_FACHG; 353 } 354 lastrow--; 355 q = TAILQ_NEXT(p, chain); 356 TAILQ_REMOVE(&netcb, p, chain); 357 free(p); 358 p = q; 359 } 360 /* 361 * Update existing connections and add new ones. 362 */ 363 TAILQ_FOREACH(p, &netcb, chain) { 364 if (p->ni_line == -1) { 365 /* 366 * Add a new entry if possible. 367 */ 368 if (lastrow > YMAX(wnd)) 369 continue; 370 p->ni_line = lastrow++; 371 p->ni_flags |= NIF_LACHG|NIF_FACHG; 372 } 373 if (p->ni_flags & NIF_LACHG) { 374 wmove(wnd, p->ni_line, LADDR); 375 inetprint((struct sockaddr *)&p->ni_lsa, p->ni_proto); 376 p->ni_flags &= ~NIF_LACHG; 377 } 378 if (p->ni_flags & NIF_FACHG) { 379 wmove(wnd, p->ni_line, FADDR); 380 inetprint((struct sockaddr *)&p->ni_fsa, p->ni_proto); 381 p->ni_flags &= ~NIF_FACHG; 382 } 383 #ifdef INET6 384 family = (p->ni_lsa.ss_family == AF_INET) ? "4" : "6"; 385 #endif 386 snprintf(proto, sizeof(proto), "%s%s", p->ni_proto, family); 387 mvwaddstr(wnd, p->ni_line, PROTO, proto); 388 mvwprintw(wnd, p->ni_line, RCVCC, "%6u", p->ni_rcvcc); 389 mvwprintw(wnd, p->ni_line, SNDCC, "%6u", p->ni_sndcc); 390 if (streq(p->ni_proto, "tcp")) { 391 if (p->ni_state < 0 || p->ni_state >= TCP_NSTATES) 392 mvwprintw(wnd, p->ni_line, STATE, "%d", 393 p->ni_state); 394 else 395 mvwaddstr(wnd, p->ni_line, STATE, 396 tcpstates[p->ni_state]); 397 } 398 wclrtoeol(wnd); 399 } 400 if (lastrow < YMAX(wnd)) { 401 wmove(wnd, lastrow, 0); wclrtobot(wnd); 402 wmove(wnd, YMAX(wnd), 0); wdeleteln(wnd); /* XXX */ 403 } 404 } 405 406 /* 407 * Pretty print an Internet address (net address + port). 408 * If the nflag was specified, use numbers instead of names. 409 */ 410 static void 411 inetprint(struct sockaddr *sa, const char *proto) 412 { 413 struct servent *sp = 0; 414 char line[80], *cp; 415 int port; 416 417 switch (sa->sa_family) { 418 case AF_INET: 419 port = ((struct sockaddr_in *)sa)->sin_port; 420 break; 421 #ifdef INET6 422 case AF_INET6: 423 port = ((struct sockaddr_in6 *)sa)->sin6_port; 424 break; 425 #endif 426 default: 427 port = 0; 428 break; 429 } 430 snprintf(line, sizeof(line), "%.*s.", 16, inetname(sa)); 431 cp = strchr(line, '\0'); 432 if (!nflag && port) 433 sp = getservbyport(port, proto); 434 if (sp || port == 0) 435 snprintf(cp, sizeof(line) - (cp - line), "%.8s", 436 sp ? sp->s_name : "*"); 437 else 438 snprintf(cp, sizeof(line) - (cp - line), "%d", 439 ntohs((u_short)port)); 440 /* pad to full column to clear any garbage */ 441 cp = strchr(line, '\0'); 442 while (cp - line < 22) 443 *cp++ = ' '; 444 line[22] = '\0'; 445 waddstr(wnd, line); 446 } 447 448 /* 449 * Construct an Internet address representation. 450 * If the nflag has been supplied, give 451 * numeric value, otherwise try for symbolic name. 452 */ 453 static char * 454 inetname(struct sockaddr *sa) 455 { 456 char *cp = 0; 457 static char line[NI_MAXHOST]; 458 struct hostent *hp; 459 struct in_addr in; 460 461 #ifdef INET6 462 if (sa->sa_family == AF_INET6) { 463 if (memcmp(&((struct sockaddr_in6 *)sa)->sin6_addr, 464 &in6addr_any, sizeof(in6addr_any)) == 0) 465 strcpy(line, "*"); 466 else 467 getnameinfo(sa, sa->sa_len, line, sizeof(line), NULL, 0, 468 nflag ? NI_NUMERICHOST : 0); 469 return (line); 470 } 471 #endif 472 473 in = ((struct sockaddr_in *)sa)->sin_addr; 474 if (!nflag && in.s_addr != INADDR_ANY) { 475 hp = gethostbyaddr((char *)&in, sizeof (in), AF_INET); 476 if (hp) 477 cp = hp->h_name; 478 } 479 if (in.s_addr == INADDR_ANY) 480 strcpy(line, "*"); 481 else if (cp) 482 snprintf(line, sizeof(line), "%s", cp); 483 else { 484 in.s_addr = ntohl(in.s_addr); 485 #define C(x) ((x) & 0xff) 486 snprintf(line, sizeof(line), "%u.%u.%u.%u", C(in.s_addr >> 24), 487 C(in.s_addr >> 16), C(in.s_addr >> 8), C(in.s_addr)); 488 } 489 return (line); 490 } 491 492 int 493 cmdnetstat(const char *cmd, const char *args) 494 { 495 if (prefix(cmd, "all")) { 496 aflag = !aflag; 497 goto fixup; 498 } 499 if (prefix(cmd, "numbers") || prefix(cmd, "names")) { 500 struct netinfo *p; 501 int new; 502 503 new = prefix(cmd, "numbers"); 504 if (new == nflag) 505 return (1); 506 TAILQ_FOREACH(p, &netcb, chain) { 507 if (p->ni_line == -1) 508 continue; 509 p->ni_flags |= NIF_LACHG|NIF_FACHG; 510 } 511 nflag = new; 512 goto redisplay; 513 } 514 if (!netcmd(cmd, args)) 515 return (0); 516 fixup: 517 fetchnetstat(); 518 redisplay: 519 shownetstat(); 520 refresh(); 521 return (1); 522 } 523