1 /* vi: set sw=4 ts=4: */
3 * Mini netstat implementation(s) for busybox
4 * based in part on the netstat implementation from net-tools.
6 * Copyright (C) 2002 by Bart Visscher <magick@linux-fan.com>
9 * IPV6 support added by Bart Visscher <magick@linux-fan.com>
12 * optional '-p' flag support ported from net-tools by G. Somlo <somlo@cmu.edu>
14 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
18 #include "inet_common.h"
20 //usage:#define netstat_trivial_usage
21 //usage: "[-"IF_ROUTE("r")"al] [-tuwx] [-en"IF_FEATURE_NETSTAT_WIDE("W")IF_FEATURE_NETSTAT_PRG("p")"]"
22 //usage:#define netstat_full_usage "\n\n"
23 //usage: "Display networking information\n"
26 //usage: "\n -r Routing table"
28 //usage: "\n -a All sockets"
29 //usage: "\n -l Listening sockets"
30 //usage: "\n Else: connected sockets"
31 //usage: "\n -t TCP sockets"
32 //usage: "\n -u UDP sockets"
33 //usage: "\n -w Raw sockets"
34 //usage: "\n -x Unix sockets"
35 //usage: "\n Else: all socket types"
36 //usage: "\n -e Other/more information"
37 //usage: "\n -n Don't resolve names"
38 //usage: IF_FEATURE_NETSTAT_WIDE(
39 //usage: "\n -W Wide display"
41 //usage: IF_FEATURE_NETSTAT_PRG(
42 //usage: "\n -p Show PID/program name for sockets"
45 #define NETSTAT_OPTS "laentuwx" \
47 IF_FEATURE_NETSTAT_WIDE("W") \
48 IF_FEATURE_NETSTAT_PRG( "p")
51 OPT_sock_listen
= 1 << 0, // l
52 OPT_sock_all
= 1 << 1, // a
53 OPT_extended
= 1 << 2, // e
54 OPT_noresolve
= 1 << 3, // n
55 OPT_sock_tcp
= 1 << 4, // t
56 OPT_sock_udp
= 1 << 5, // u
57 OPT_sock_raw
= 1 << 6, // w
58 OPT_sock_unix
= 1 << 7, // x
60 IF_ROUTE( OPTBIT_ROUTE
,)
61 IF_FEATURE_NETSTAT_WIDE(OPTBIT_WIDE
,)
62 IF_FEATURE_NETSTAT_PRG( OPTBIT_PRG
,)
63 OPT_route
= IF_ROUTE( (1 << OPTBIT_ROUTE
)) + 0, // r
64 OPT_wide
= IF_FEATURE_NETSTAT_WIDE((1 << OPTBIT_WIDE
)) + 0, // W
65 OPT_prg
= IF_FEATURE_NETSTAT_PRG( (1 << OPTBIT_PRG
)) + 0, // p
68 #define NETSTAT_CONNECTED 0x01
69 #define NETSTAT_LISTENING 0x02
70 #define NETSTAT_NUMERIC 0x04
71 /* Must match getopt32 option string */
72 #define NETSTAT_TCP 0x10
73 #define NETSTAT_UDP 0x20
74 #define NETSTAT_RAW 0x40
75 #define NETSTAT_UNIX 0x80
76 #define NETSTAT_ALLPROTO (NETSTAT_TCP|NETSTAT_UDP|NETSTAT_RAW|NETSTAT_UNIX)
90 TCP_CLOSING
, /* now a valid state */
93 static const char *const tcp_state
[] = {
109 SS_FREE
= 0, /* not allocated */
110 SS_UNCONNECTED
, /* unconnected to any socket */
111 SS_CONNECTING
, /* in process of connecting */
112 SS_CONNECTED
, /* connected to socket */
113 SS_DISCONNECTING
/* in process of disconnecting */
116 #define SO_ACCEPTCON (1<<16) /* performed a listen */
117 #define SO_WAITDATA (1<<17) /* wait data to read */
118 #define SO_NOSPACE (1<<18) /* no space to write */
120 #define ADDR_NORMAL_WIDTH 23
121 /* When there are IPv6 connections the IPv6 addresses will be
122 * truncated to none-recognition. The '-W' option makes the
123 * address columns wide enough to accomodate for longest possible
124 * IPv6 addresses, i.e. addresses of the form
125 * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:ddd.ddd.ddd.ddd
127 #define ADDR_WIDE 51 /* INET6_ADDRSTRLEN + 5 for the port number */
128 #if ENABLE_FEATURE_NETSTAT_WIDE
129 # define FMT_NET_CONN_DATA "%s %6ld %6ld %-*s %-*s %-12s"
130 # define FMT_NET_CONN_HEADER "\nProto Recv-Q Send-Q %-*s %-*s State %s\n"
132 # define FMT_NET_CONN_DATA "%s %6ld %6ld %-23s %-23s %-12s"
133 # define FMT_NET_CONN_HEADER "\nProto Recv-Q Send-Q %-23s %-23s State %s\n"
136 #define PROGNAME_WIDTH 20
137 #define PROGNAME_WIDTH_STR "20"
138 /* PROGNAME_WIDTH chars: 12345678901234567890 */
139 #define PROGNAME_BANNER "PID/Program name "
142 struct prg_node
*next
;
144 char name
[PROGNAME_WIDTH
];
147 #define PRG_HASH_SIZE 211
151 #if ENABLE_FEATURE_NETSTAT_PRG
152 smallint prg_cache_loaded
;
153 struct prg_node
*prg_hash
[PRG_HASH_SIZE
];
155 #if ENABLE_FEATURE_NETSTAT_PRG
156 const char *progname_banner
;
158 #if ENABLE_FEATURE_NETSTAT_WIDE
162 #define G (*ptr_to_globals)
163 #define flags (G.flags )
164 #define prg_cache_loaded (G.prg_cache_loaded)
165 #define prg_hash (G.prg_hash )
166 #if ENABLE_FEATURE_NETSTAT_PRG
167 # define progname_banner (G.progname_banner )
169 # define progname_banner ""
171 #define INIT_G() do { \
172 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
173 flags = NETSTAT_CONNECTED | NETSTAT_ALLPROTO; \
177 #if ENABLE_FEATURE_NETSTAT_PRG
179 /* Deliberately truncating long to unsigned *int* */
180 #define PRG_HASHIT(x) ((unsigned)(x) % PRG_HASH_SIZE)
182 static void prg_cache_add(long inode
, char *name
)
184 unsigned hi
= PRG_HASHIT(inode
);
185 struct prg_node
**pnp
, *pn
;
187 prg_cache_loaded
= 2;
188 for (pnp
= prg_hash
+ hi
; (pn
= *pnp
) != NULL
; pnp
= &pn
->next
) {
189 if (pn
->inode
== inode
) {
190 /* Some warning should be appropriate here
191 as we got multiple processes for one i-node */
195 *pnp
= xzalloc(sizeof(struct prg_node
));
198 safe_strncpy(pn
->name
, name
, PROGNAME_WIDTH
);
201 static const char *prg_cache_get(long inode
)
203 unsigned hi
= PRG_HASHIT(inode
);
206 for (pn
= prg_hash
[hi
]; pn
; pn
= pn
->next
)
207 if (pn
->inode
== inode
)
212 #if ENABLE_FEATURE_CLEAN_UP
213 static void prg_cache_clear(void)
215 struct prg_node
**pnp
, *pn
;
217 for (pnp
= prg_hash
; pnp
< prg_hash
+ PRG_HASH_SIZE
; pnp
++) {
218 while ((pn
= *pnp
) != NULL
) {
225 #define prg_cache_clear() ((void)0)
228 static long extract_socket_inode(const char *lname
)
232 if (strncmp(lname
, "socket:[", sizeof("socket:[")-1) == 0) {
233 /* "socket:[12345]", extract the "12345" as inode */
234 inode
= bb_strtoul(lname
+ sizeof("socket:[")-1, (char**)&lname
, 0);
237 } else if (strncmp(lname
, "[0000]:", sizeof("[0000]:")-1) == 0) {
238 /* "[0000]:12345", extract the "12345" as inode */
239 inode
= bb_strtoul(lname
+ sizeof("[0000]:")-1, NULL
, 0);
240 if (errno
) /* not NUL terminated? */
244 #if 0 /* bb_strtol returns all-ones bit pattern on ERANGE anyway */
251 static int FAST_FUNC
add_to_prg_cache_if_socket(const char *fileName
,
252 struct stat
*statbuf UNUSED_PARAM
,
253 void *pid_slash_progname
,
254 int depth UNUSED_PARAM
)
259 linkname
= xmalloc_readlink(fileName
);
260 if (linkname
!= NULL
) {
261 inode
= extract_socket_inode(linkname
);
264 prg_cache_add(inode
, (char *)pid_slash_progname
);
269 static int FAST_FUNC
dir_act(const char *fileName
,
270 struct stat
*statbuf UNUSED_PARAM
,
271 void *userData UNUSED_PARAM
,
275 char *pid_slash_progname
;
276 char proc_pid_fname
[sizeof("/proc/%u/cmdline") + sizeof(long)*3];
277 char cmdline_buf
[512];
280 if (depth
== 0) /* "/proc" itself */
281 return TRUE
; /* continue looking one level below /proc */
283 pid
= fileName
+ sizeof("/proc/")-1; /* point after "/proc/" */
284 if (!isdigit(pid
[0])) /* skip /proc entries which aren't processes */
287 len
= snprintf(proc_pid_fname
, sizeof(proc_pid_fname
), "%s/cmdline", fileName
);
288 n
= open_read_close(proc_pid_fname
, cmdline_buf
, sizeof(cmdline_buf
) - 1);
291 cmdline_buf
[n
] = '\0';
293 /* go through all files in /proc/PID/fd and check whether they are sockets */
294 strcpy(proc_pid_fname
+ len
- (sizeof("cmdline")-1), "fd");
295 pid_slash_progname
= concat_path_file(pid
, bb_basename(cmdline_buf
)); /* "PID/argv0" */
296 n
= recursive_action(proc_pid_fname
,
297 ACTION_RECURSE
| ACTION_QUIET
,
298 add_to_prg_cache_if_socket
,
300 (void *)pid_slash_progname
,
302 free(pid_slash_progname
);
305 return FALSE
; /* signal permissions error to caller */
307 return SKIP
; /* caller should not recurse further into this dir */
310 static void prg_cache_load(void)
314 prg_cache_loaded
= 1;
315 load_ok
= recursive_action("/proc", ACTION_RECURSE
| ACTION_QUIET
,
316 NULL
, dir_act
, NULL
, 0);
320 if (prg_cache_loaded
== 1)
321 bb_error_msg("can't scan /proc - are you root?");
323 bb_error_msg("showing only processes with your user ID");
328 #define prg_cache_clear() ((void)0)
330 #endif //ENABLE_FEATURE_NETSTAT_PRG
333 #if ENABLE_FEATURE_IPV6
334 static void build_ipv6_addr(char* local_addr
, struct sockaddr_in6
* localaddr
)
336 char addr6
[INET6_ADDRSTRLEN
];
339 sscanf(local_addr
, "%08X%08X%08X%08X",
340 &in6
.s6_addr32
[0], &in6
.s6_addr32
[1],
341 &in6
.s6_addr32
[2], &in6
.s6_addr32
[3]);
342 inet_ntop(AF_INET6
, &in6
, addr6
, sizeof(addr6
));
343 inet_pton(AF_INET6
, addr6
, &localaddr
->sin6_addr
);
345 localaddr
->sin6_family
= AF_INET6
;
349 static void build_ipv4_addr(char* local_addr
, struct sockaddr_in
* localaddr
)
351 sscanf(local_addr
, "%X", &localaddr
->sin_addr
.s_addr
);
352 localaddr
->sin_family
= AF_INET
;
355 static const char *get_sname(int port
, const char *proto
, int numeric
)
360 struct servent
*se
= getservbyport(port
, proto
);
364 /* hummm, we may return static buffer here!! */
365 return itoa(ntohs(port
));
368 static char *ip_port_str(struct sockaddr
*addr
, int port
, const char *proto
, int numeric
)
370 char *host
, *host_port
;
372 /* Code which used "*" for INADDR_ANY is removed: it's ambiguous
373 * in IPv6, while "0.0.0.0" is not. */
375 host
= numeric
? xmalloc_sockaddr2dotted_noport(addr
)
376 : xmalloc_sockaddr2host_noport(addr
);
378 host_port
= xasprintf("%s:%s", host
, get_sname(htons(port
), proto
, numeric
));
384 int local_port
, rem_port
, state
, uid
;
387 struct sockaddr_in sin
;
388 #if ENABLE_FEATURE_IPV6
389 struct sockaddr_in6 sin6
;
391 } localaddr
, remaddr
;
392 unsigned long rxq
, txq
, inode
;
395 static int scan_inet_proc_line(struct inet_params
*param
, char *line
)
398 /* IPv6 /proc files use 32-char hex representation
399 * of IPv6 address, followed by :PORT_IN_HEX
401 char local_addr
[33], rem_addr
[33]; /* 32 + 1 for NUL */
404 "%*d: %32[0-9A-Fa-f]:%X "
405 "%32[0-9A-Fa-f]:%X %X "
408 local_addr
, ¶m
->local_port
,
409 rem_addr
, ¶m
->rem_port
, ¶m
->state
,
410 ¶m
->txq
, ¶m
->rxq
,
411 ¶m
->uid
, ¶m
->inode
);
413 return 1; /* error */
416 if (strlen(local_addr
) > 8) {
417 #if ENABLE_FEATURE_IPV6
418 build_ipv6_addr(local_addr
, ¶m
->localaddr
.sin6
);
419 build_ipv6_addr(rem_addr
, ¶m
->remaddr
.sin6
);
422 build_ipv4_addr(local_addr
, ¶m
->localaddr
.sin
);
423 build_ipv4_addr(rem_addr
, ¶m
->remaddr
.sin
);
428 static void print_inet_line(struct inet_params
*param
,
429 const char *state_str
, const char *proto
, int is_connected
)
431 if ((is_connected
&& (flags
& NETSTAT_CONNECTED
))
432 || (!is_connected
&& (flags
& NETSTAT_LISTENING
))
434 char *l
= ip_port_str(
435 ¶m
->localaddr
.sa
, param
->local_port
,
436 proto
, flags
& NETSTAT_NUMERIC
);
437 char *r
= ip_port_str(
438 ¶m
->remaddr
.sa
, param
->rem_port
,
439 proto
, flags
& NETSTAT_NUMERIC
);
440 printf(FMT_NET_CONN_DATA
,
441 proto
, param
->rxq
, param
->txq
,
442 IF_FEATURE_NETSTAT_WIDE(G
.addr_width
,) l
,
443 IF_FEATURE_NETSTAT_WIDE(G
.addr_width
,) r
,
445 #if ENABLE_FEATURE_NETSTAT_PRG
446 if (option_mask32
& OPT_prg
)
447 printf("%."PROGNAME_WIDTH_STR
"s", prg_cache_get(param
->inode
));
455 static int FAST_FUNC
tcp_do_one(char *line
)
457 struct inet_params param
;
459 memset(¶m
, 0, sizeof(param
));
460 if (scan_inet_proc_line(¶m
, line
))
463 print_inet_line(¶m
, tcp_state
[param
.state
], "tcp", param
.rem_port
);
467 #if ENABLE_FEATURE_IPV6
468 # define NOT_NULL_ADDR(A) ( \
469 ( (A.sa.sa_family == AF_INET6) \
470 && (A.sin6.sin6_addr.s6_addr32[0] | A.sin6.sin6_addr.s6_addr32[1] | \
471 A.sin6.sin6_addr.s6_addr32[2] | A.sin6.sin6_addr.s6_addr32[3]) \
473 (A.sa.sa_family == AF_INET) \
474 && A.sin.sin_addr.s_addr != 0 \
478 # define NOT_NULL_ADDR(A) (A.sin.sin_addr.s_addr)
481 static int FAST_FUNC
udp_do_one(char *line
)
484 const char *state_str
;
485 struct inet_params param
;
487 memset(¶m
, 0, sizeof(param
)); /* otherwise we display garbage IPv6 scope_ids */
488 if (scan_inet_proc_line(¶m
, line
))
491 state_str
= "UNKNOWN";
492 switch (param
.state
) {
493 case TCP_ESTABLISHED
:
494 state_str
= "ESTABLISHED";
501 have_remaddr
= NOT_NULL_ADDR(param
.remaddr
);
502 print_inet_line(¶m
, state_str
, "udp", have_remaddr
);
506 static int FAST_FUNC
raw_do_one(char *line
)
509 struct inet_params param
;
511 if (scan_inet_proc_line(¶m
, line
))
514 have_remaddr
= NOT_NULL_ADDR(param
.remaddr
);
515 print_inet_line(¶m
, itoa(param
.state
), "raw", have_remaddr
);
519 static int FAST_FUNC
unix_do_one(char *line
)
521 unsigned long refcnt
, proto
, unix_flags
;
525 const char *ss_proto
, *ss_state
, *ss_type
;
528 /* 2.6.15 may report lines like "... @/tmp/fam-user-^@^@^@^@^@^@^@..."
529 * Other users report long lines filled by NUL bytes.
530 * (those ^@ are NUL bytes too). We see them as empty lines. */
534 path_ofs
= 0; /* paranoia */
535 num
= sscanf(line
, "%*p: %lX %lX %lX %X %X %lu %n",
536 &refcnt
, &proto
, &unix_flags
, &type
, &state
, &inode
, &path_ofs
);
538 return 1; /* error */
540 if ((flags
& (NETSTAT_LISTENING
|NETSTAT_CONNECTED
)) != (NETSTAT_LISTENING
|NETSTAT_CONNECTED
)) {
541 if ((state
== SS_UNCONNECTED
) && (unix_flags
& SO_ACCEPTCON
)) {
542 if (!(flags
& NETSTAT_LISTENING
))
545 if (!(flags
& NETSTAT_CONNECTED
))
572 ss_type
= "SEQPACKET";
584 * Unconnected sockets may be listening
587 if (unix_flags
& SO_ACCEPTCON
) {
588 ss_state
= "LISTENING";
594 ss_state
= "CONNECTING";
597 ss_state
= "CONNECTED";
599 case SS_DISCONNECTING
:
600 ss_state
= "DISCONNECTING";
603 ss_state
= "UNKNOWN";
606 strcpy(ss_flags
, "[ ");
607 if (unix_flags
& SO_ACCEPTCON
)
608 strcat(ss_flags
, "ACC ");
609 if (unix_flags
& SO_WAITDATA
)
610 strcat(ss_flags
, "W ");
611 if (unix_flags
& SO_NOSPACE
)
612 strcat(ss_flags
, "N ");
613 strcat(ss_flags
, "]");
615 printf("%-5s %-6ld %-11s %-10s %-13s %6lu ",
616 ss_proto
, refcnt
, ss_flags
, ss_type
, ss_state
, inode
619 #if ENABLE_FEATURE_NETSTAT_PRG
620 if (option_mask32
& OPT_prg
)
621 printf("%-"PROGNAME_WIDTH_STR
"s", prg_cache_get(inode
));
624 /* TODO: currently we stop at first NUL byte. Is it a problem? */
626 *strchrnul(line
, '\n') = '\0';
628 fputc_printable(*line
++, stdout
);
633 static void do_info(const char *file
, int FAST_FUNC (*proc
)(char *))
639 /* _stdin is just to save "r" param */
640 procinfo
= fopen_or_warn_stdin(file
);
641 if (procinfo
== NULL
) {
645 /* Why xmalloc_fgets_str? because it doesn't stop on NULs */
646 while ((buffer
= xmalloc_fgets_str(procinfo
, "\n")) != NULL
) {
647 /* line 0 is skipped */
648 if (lnr
&& proc(buffer
))
649 bb_error_msg("%s: bogus data on line %d", file
, lnr
+ 1);
656 int netstat_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
657 int netstat_main(int argc UNUSED_PARAM
, char **argv
)
663 /* Option string must match NETSTAT_xxx constants */
664 opt
= getopt32(argv
, NETSTAT_OPTS
);
665 if (opt
& OPT_sock_listen
) { // -l
666 flags
&= ~NETSTAT_CONNECTED
;
667 flags
|= NETSTAT_LISTENING
;
669 if (opt
& OPT_sock_all
) flags
|= NETSTAT_LISTENING
| NETSTAT_CONNECTED
; // -a
670 //if (opt & OPT_extended) // -e
671 if (opt
& OPT_noresolve
) flags
|= NETSTAT_NUMERIC
; // -n
672 //if (opt & OPT_sock_tcp) // -t: NETSTAT_TCP
673 //if (opt & OPT_sock_udp) // -u: NETSTAT_UDP
674 //if (opt & OPT_sock_raw) // -w: NETSTAT_RAW
675 //if (opt & OPT_sock_unix) // -x: NETSTAT_UNIX
677 if (opt
& OPT_route
) { // -r
678 bb_displayroutes(flags
& NETSTAT_NUMERIC
, !(opt
& OPT_extended
));
682 #if ENABLE_FEATURE_NETSTAT_WIDE
683 G
.addr_width
= ADDR_NORMAL_WIDTH
;
684 if (opt
& OPT_wide
) { // -W
685 G
.addr_width
= ADDR_WIDE
;
688 #if ENABLE_FEATURE_NETSTAT_PRG
689 progname_banner
= "";
690 if (opt
& OPT_prg
) { // -p
691 progname_banner
= PROGNAME_BANNER
;
696 opt
&= NETSTAT_ALLPROTO
;
698 flags
&= ~NETSTAT_ALLPROTO
;
701 if (flags
& (NETSTAT_TCP
|NETSTAT_UDP
|NETSTAT_RAW
)) {
702 printf("Active Internet connections "); /* xxx */
704 if ((flags
& (NETSTAT_LISTENING
|NETSTAT_CONNECTED
)) == (NETSTAT_LISTENING
|NETSTAT_CONNECTED
))
705 printf("(servers and established)");
706 else if (flags
& NETSTAT_LISTENING
)
707 printf("(only servers)");
709 printf("(w/o servers)");
710 printf(FMT_NET_CONN_HEADER
,
711 IF_FEATURE_NETSTAT_WIDE(G
.addr_width
,) "Local Address",
712 IF_FEATURE_NETSTAT_WIDE(G
.addr_width
,) "Foreign Address",
716 if (flags
& NETSTAT_TCP
) {
717 do_info("/proc/net/tcp", tcp_do_one
);
718 #if ENABLE_FEATURE_IPV6
719 do_info("/proc/net/tcp6", tcp_do_one
);
722 if (flags
& NETSTAT_UDP
) {
723 do_info("/proc/net/udp", udp_do_one
);
724 #if ENABLE_FEATURE_IPV6
725 do_info("/proc/net/udp6", udp_do_one
);
728 if (flags
& NETSTAT_RAW
) {
729 do_info("/proc/net/raw", raw_do_one
);
730 #if ENABLE_FEATURE_IPV6
731 do_info("/proc/net/raw6", raw_do_one
);
734 if (flags
& NETSTAT_UNIX
) {
735 printf("Active UNIX domain sockets ");
736 if ((flags
& (NETSTAT_LISTENING
|NETSTAT_CONNECTED
)) == (NETSTAT_LISTENING
|NETSTAT_CONNECTED
))
737 printf("(servers and established)");
738 else if (flags
& NETSTAT_LISTENING
)
739 printf("(only servers)");
741 printf("(w/o servers)");
742 printf("\nProto RefCnt Flags Type State I-Node %sPath\n", progname_banner
);
743 do_info("/proc/net/unix", unix_do_one
);