Bump version to 1.32.0
[oweals/busybox.git] / networking / netstat.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini netstat implementation(s) for busybox
4  * based in part on the netstat implementation from net-tools.
5  *
6  * Copyright (C) 2002 by Bart Visscher <magick@linux-fan.com>
7  *
8  * 2002-04-20
9  * IPV6 support added by Bart Visscher <magick@linux-fan.com>
10  *
11  * 2008-07-10
12  * optional '-p' flag support ported from net-tools by G. Somlo <somlo@cmu.edu>
13  *
14  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
15  */
16 //config:config NETSTAT
17 //config:       bool "netstat (10 kb)"
18 //config:       default y
19 //config:       select PLATFORM_LINUX
20 //config:       help
21 //config:       netstat prints information about the Linux networking subsystem.
22 //config:
23 //config:config FEATURE_NETSTAT_WIDE
24 //config:       bool "Enable wide output"
25 //config:       default y
26 //config:       depends on NETSTAT
27 //config:       help
28 //config:       Add support for wide columns. Useful when displaying IPv6 addresses
29 //config:       (-W option).
30 //config:
31 //config:config FEATURE_NETSTAT_PRG
32 //config:       bool "Enable PID/Program name output"
33 //config:       default y
34 //config:       depends on NETSTAT
35 //config:       help
36 //config:       Add support for -p flag to print out PID and program name.
37 //config:       +700 bytes of code.
38
39 //applet:IF_NETSTAT(APPLET(netstat, BB_DIR_BIN, BB_SUID_DROP))
40
41 //kbuild:lib-$(CONFIG_NETSTAT) += netstat.o
42
43 #include "libbb.h"
44 #include "inet_common.h"
45
46 //usage:#define netstat_trivial_usage
47 //usage:       "[-"IF_ROUTE("r")"al] [-tuwx] [-en"IF_FEATURE_NETSTAT_WIDE("W")IF_FEATURE_NETSTAT_PRG("p")"]"
48 //usage:#define netstat_full_usage "\n\n"
49 //usage:       "Display networking information\n"
50 //usage:        IF_ROUTE(
51 //usage:     "\n        -r      Routing table"
52 //usage:        )
53 //usage:     "\n        -a      All sockets"
54 //usage:     "\n        -l      Listening sockets"
55 //usage:     "\n                Else: connected sockets"
56 //usage:     "\n        -t      TCP sockets"
57 //usage:     "\n        -u      UDP sockets"
58 //usage:     "\n        -w      Raw sockets"
59 //usage:     "\n        -x      Unix sockets"
60 //usage:     "\n                Else: all socket types"
61 //usage:     "\n        -e      Other/more information"
62 //usage:     "\n        -n      Don't resolve names"
63 //usage:        IF_FEATURE_NETSTAT_WIDE(
64 //usage:     "\n        -W      Wide display"
65 //usage:        )
66 //usage:        IF_FEATURE_NETSTAT_PRG(
67 //usage:     "\n        -p      Show PID/program name for sockets"
68 //usage:        )
69
70 #define NETSTAT_OPTS "laentuwx" \
71         IF_ROUTE(               "r") \
72         IF_FEATURE_NETSTAT_WIDE("W") \
73         IF_FEATURE_NETSTAT_PRG( "p")
74
75 enum {
76         OPT_sock_listen = 1 << 0, // l
77         OPT_sock_all    = 1 << 1, // a
78         OPT_extended    = 1 << 2, // e
79         OPT_noresolve   = 1 << 3, // n
80         OPT_sock_tcp    = 1 << 4, // t
81         OPT_sock_udp    = 1 << 5, // u
82         OPT_sock_raw    = 1 << 6, // w
83         OPT_sock_unix   = 1 << 7, // x
84         OPTBIT_x        = 7,
85         IF_ROUTE(               OPTBIT_ROUTE,)
86         IF_FEATURE_NETSTAT_WIDE(OPTBIT_WIDE ,)
87         IF_FEATURE_NETSTAT_PRG( OPTBIT_PRG  ,)
88         OPT_route       = IF_ROUTE(               (1 << OPTBIT_ROUTE)) + 0, // r
89         OPT_wide        = IF_FEATURE_NETSTAT_WIDE((1 << OPTBIT_WIDE )) + 0, // W
90         OPT_prg         = IF_FEATURE_NETSTAT_PRG( (1 << OPTBIT_PRG  )) + 0, // p
91 };
92
93 #define NETSTAT_CONNECTED 0x01
94 #define NETSTAT_LISTENING 0x02
95 #define NETSTAT_NUMERIC   0x04
96 /* Must match getopt32 option string */
97 #define NETSTAT_TCP       0x10
98 #define NETSTAT_UDP       0x20
99 #define NETSTAT_RAW       0x40
100 #define NETSTAT_UNIX      0x80
101 #define NETSTAT_ALLPROTO  (NETSTAT_TCP|NETSTAT_UDP|NETSTAT_RAW|NETSTAT_UNIX)
102
103
104 enum {
105         TCP_ESTABLISHED = 1,
106         TCP_SYN_SENT,
107         TCP_SYN_RECV,
108         TCP_FIN_WAIT1,
109         TCP_FIN_WAIT2,
110         TCP_TIME_WAIT,
111         TCP_CLOSE,
112         TCP_CLOSE_WAIT,
113         TCP_LAST_ACK,
114         TCP_LISTEN,
115         TCP_CLOSING, /* now a valid state */
116 };
117
118 static const char *const tcp_state[] = {
119         "",
120         "ESTABLISHED",
121         "SYN_SENT",
122         "SYN_RECV",
123         "FIN_WAIT1",
124         "FIN_WAIT2",
125         "TIME_WAIT",
126         "CLOSE",
127         "CLOSE_WAIT",
128         "LAST_ACK",
129         "LISTEN",
130         "CLOSING"
131 };
132
133 typedef enum {
134         SS_FREE = 0,     /* not allocated                */
135         SS_UNCONNECTED,  /* unconnected to any socket    */
136         SS_CONNECTING,   /* in process of connecting     */
137         SS_CONNECTED,    /* connected to socket          */
138         SS_DISCONNECTING /* in process of disconnecting  */
139 } socket_state;
140
141 #define SO_ACCEPTCON (1<<16)  /* performed a listen           */
142 #define SO_WAITDATA  (1<<17)  /* wait data to read            */
143 #define SO_NOSPACE   (1<<18)  /* no space to write            */
144
145 #define ADDR_NORMAL_WIDTH        23
146 /* When there are IPv6 connections the IPv6 addresses will be
147  * truncated to none-recognition. The '-W' option makes the
148  * address columns wide enough to accommodate for longest possible
149  * IPv6 addresses, i.e. addresses of the form
150  * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:ddd.ddd.ddd.ddd
151  */
152 #define ADDR_WIDE                51  /* INET6_ADDRSTRLEN + 5 for the port number */
153 #if ENABLE_FEATURE_NETSTAT_WIDE
154 # define FMT_NET_CONN_DATA       "%s   %6lu %6lu %-*s %-*s %-12s"
155 # define FMT_NET_CONN_HEADER     "\nProto Recv-Q Send-Q %-*s %-*s State       %s\n"
156 #else
157 # define FMT_NET_CONN_DATA       "%s   %6lu %6lu %-23s %-23s %-12s"
158 # define FMT_NET_CONN_HEADER     "\nProto Recv-Q Send-Q %-23s %-23s State       %s\n"
159 #endif
160
161 #define PROGNAME_WIDTH     20
162 #define PROGNAME_WIDTH_STR "20"
163 /* PROGNAME_WIDTH chars: 12345678901234567890 */
164 #define PROGNAME_BANNER "PID/Program name    "
165
166 struct prg_node {
167         struct prg_node *next;
168         long inode;
169         char name[PROGNAME_WIDTH];
170 };
171
172 #define PRG_HASH_SIZE 211
173
174 struct globals {
175         smalluint flags;
176 #if ENABLE_FEATURE_NETSTAT_PRG
177         smallint prg_cache_loaded;
178         struct prg_node *prg_hash[PRG_HASH_SIZE];
179 #endif
180 #if ENABLE_FEATURE_NETSTAT_PRG
181         const char *progname_banner;
182 #endif
183 #if ENABLE_FEATURE_NETSTAT_WIDE
184         unsigned addr_width;
185 #endif
186 };
187 #define G (*ptr_to_globals)
188 #define flags            (G.flags           )
189 #define prg_cache_loaded (G.prg_cache_loaded)
190 #define prg_hash         (G.prg_hash        )
191 #if ENABLE_FEATURE_NETSTAT_PRG
192 # define progname_banner (G.progname_banner )
193 #else
194 # define progname_banner ""
195 #endif
196 #define INIT_G() do { \
197         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
198         flags = NETSTAT_CONNECTED | NETSTAT_ALLPROTO; \
199 } while (0)
200
201
202 #if ENABLE_FEATURE_NETSTAT_PRG
203
204 /* Deliberately truncating long to unsigned *int* */
205 #define PRG_HASHIT(x) ((unsigned)(x) % PRG_HASH_SIZE)
206
207 static void prg_cache_add(long inode, char *name)
208 {
209         unsigned hi = PRG_HASHIT(inode);
210         struct prg_node **pnp, *pn;
211
212         prg_cache_loaded = 2;
213         for (pnp = prg_hash + hi; (pn = *pnp) != NULL; pnp = &pn->next) {
214                 if (pn->inode == inode) {
215                         /* Some warning should be appropriate here
216                          * as we got multiple processes for one i-node */
217                         return;
218                 }
219         }
220         *pnp = xzalloc(sizeof(struct prg_node));
221         pn = *pnp;
222         pn->inode = inode;
223         safe_strncpy(pn->name, name, PROGNAME_WIDTH);
224 }
225
226 static const char *prg_cache_get(long inode)
227 {
228         unsigned hi = PRG_HASHIT(inode);
229         struct prg_node *pn;
230
231         for (pn = prg_hash[hi]; pn; pn = pn->next)
232                 if (pn->inode == inode)
233                         return pn->name;
234         return "-";
235 }
236
237 #if ENABLE_FEATURE_CLEAN_UP
238 static void prg_cache_clear(void)
239 {
240         struct prg_node **pnp, *pn;
241
242         for (pnp = prg_hash; pnp < prg_hash + PRG_HASH_SIZE; pnp++) {
243                 while ((pn = *pnp) != NULL) {
244                         *pnp = pn->next;
245                         free(pn);
246                 }
247         }
248 }
249 #else
250 #define prg_cache_clear() ((void)0)
251 #endif
252
253 static long extract_socket_inode(const char *lname)
254 {
255         long inode = -1;
256
257         if (is_prefixed_with(lname, "socket:[")) {
258                 /* "socket:[12345]", extract the "12345" as inode */
259                 inode = bb_strtoul(lname + sizeof("socket:[")-1, (char**)&lname, 0);
260                 if (*lname != ']')
261                         inode = -1;
262         } else if (is_prefixed_with(lname, "[0000]:")) {
263                 /* "[0000]:12345", extract the "12345" as inode */
264                 inode = bb_strtoul(lname + sizeof("[0000]:")-1, NULL, 0);
265                 if (errno) /* not NUL terminated? */
266                         inode = -1;
267         }
268
269 #if 0 /* bb_strtol returns all-ones bit pattern on ERANGE anyway */
270         if (errno == ERANGE)
271                 inode = -1;
272 #endif
273         return inode;
274 }
275
276 static int FAST_FUNC add_to_prg_cache_if_socket(const char *fileName,
277                 struct stat *statbuf UNUSED_PARAM,
278                 void *pid_slash_progname,
279                 int depth UNUSED_PARAM)
280 {
281         char *linkname;
282         long inode;
283
284         linkname = xmalloc_readlink(fileName);
285         if (linkname != NULL) {
286                 inode = extract_socket_inode(linkname);
287                 free(linkname);
288                 if (inode >= 0)
289                         prg_cache_add(inode, (char *)pid_slash_progname);
290         }
291         return TRUE;
292 }
293
294 static int FAST_FUNC dir_act(const char *fileName,
295                 struct stat *statbuf UNUSED_PARAM,
296                 void *userData UNUSED_PARAM,
297                 int depth)
298 {
299         const char *pid;
300         char *pid_slash_progname;
301         char proc_pid_fname[sizeof("/proc/%u/cmdline") + sizeof(long)*3];
302         char cmdline_buf[512];
303         int n, len;
304
305         if (depth == 0) /* "/proc" itself */
306                 return TRUE; /* continue looking one level below /proc */
307
308         pid = fileName + sizeof("/proc/")-1; /* point after "/proc/" */
309         if (!isdigit(pid[0])) /* skip /proc entries which aren't processes */
310                 return SKIP;
311
312         len = snprintf(proc_pid_fname, sizeof(proc_pid_fname), "%s/cmdline", fileName);
313         n = open_read_close(proc_pid_fname, cmdline_buf, sizeof(cmdline_buf) - 1);
314         if (n < 0)
315                 return FALSE;
316         cmdline_buf[n] = '\0';
317
318         /* go through all files in /proc/PID/fd and check whether they are sockets */
319         strcpy(proc_pid_fname + len - (sizeof("cmdline")-1), "fd");
320         pid_slash_progname = concat_path_file(pid, bb_basename(cmdline_buf)); /* "PID/argv0" */
321         n = recursive_action(proc_pid_fname,
322                         ACTION_RECURSE | ACTION_QUIET,
323                         add_to_prg_cache_if_socket,
324                         NULL,
325                         (void *)pid_slash_progname,
326                         0);
327         free(pid_slash_progname);
328
329         if (!n)
330                 return FALSE; /* signal permissions error to caller */
331
332         return SKIP; /* caller should not recurse further into this dir */
333 }
334
335 static void prg_cache_load(void)
336 {
337         int load_ok;
338
339         prg_cache_loaded = 1;
340         load_ok = recursive_action("/proc", ACTION_RECURSE | ACTION_QUIET,
341                                 NULL, dir_act, NULL, 0);
342         if (load_ok)
343                 return;
344
345         if (prg_cache_loaded == 1)
346                 bb_simple_error_msg("can't scan /proc - are you root?");
347         else
348                 bb_simple_error_msg("showing only processes with your user ID");
349 }
350
351 #else
352
353 #define prg_cache_clear()       ((void)0)
354
355 #endif //ENABLE_FEATURE_NETSTAT_PRG
356
357
358 #if ENABLE_FEATURE_IPV6
359 static void build_ipv6_addr(char* local_addr, struct sockaddr_in6* localaddr)
360 {
361         char addr6[INET6_ADDRSTRLEN];
362         struct in6_addr in6;
363
364         sscanf(local_addr, "%08X%08X%08X%08X",
365                    &in6.s6_addr32[0], &in6.s6_addr32[1],
366                    &in6.s6_addr32[2], &in6.s6_addr32[3]);
367         inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6));
368         inet_pton(AF_INET6, addr6, &localaddr->sin6_addr);
369
370         localaddr->sin6_family = AF_INET6;
371 }
372 #endif
373
374 static void build_ipv4_addr(char* local_addr, struct sockaddr_in* localaddr)
375 {
376         sscanf(local_addr, "%X", &localaddr->sin_addr.s_addr);
377         localaddr->sin_family = AF_INET;
378 }
379
380 static const char *get_sname(int port, const char *proto, int numeric)
381 {
382         if (!port)
383                 return "*";
384         if (!numeric) {
385                 struct servent *se = getservbyport(port, proto);
386                 if (se)
387                         return se->s_name;
388         }
389         /* hummm, we may return static buffer here!! */
390         return itoa(ntohs(port));
391 }
392
393 static char *ip_port_str(struct sockaddr *addr, int port, const char *proto, int numeric)
394 {
395         char *host, *host_port;
396
397         /* Code which used "*" for INADDR_ANY is removed: it's ambiguous
398          * in IPv6, while "0.0.0.0" is not. */
399
400         host = NULL;
401         if (!numeric)
402                 host = xmalloc_sockaddr2host_noport(addr);
403         if (!host)
404                 host = xmalloc_sockaddr2dotted_noport(addr);
405
406         host_port = xasprintf("%s:%s", host, get_sname(htons(port), proto, numeric));
407         free(host);
408         return host_port;
409 }
410
411 struct inet_params {
412         int local_port, rem_port, state, uid;
413         union {
414                 struct sockaddr     sa;
415                 struct sockaddr_in  sin;
416 #if ENABLE_FEATURE_IPV6
417                 struct sockaddr_in6 sin6;
418 #endif
419         } localaddr, remaddr;
420         unsigned long rxq, txq, inode;
421 };
422
423 static int scan_inet_proc_line(struct inet_params *param, char *line)
424 {
425         int num;
426         /* IPv6 /proc files use 32-char hex representation
427          * of IPv6 address, followed by :PORT_IN_HEX
428          */
429         char local_addr[33], rem_addr[33]; /* 32 + 1 for NUL */
430
431         num = sscanf(line,
432                         "%*d: %32[0-9A-Fa-f]:%X "
433                         "%32[0-9A-Fa-f]:%X %X "
434                         "%lX:%lX %*X:%*X "
435                         "%*X %d %*d %lu ",
436                         local_addr, &param->local_port,
437                         rem_addr, &param->rem_port, &param->state,
438                         &param->txq, &param->rxq,
439                         &param->uid, &param->inode);
440         if (num < 9) {
441                 return 1; /* error */
442         }
443
444         if (strlen(local_addr) > 8) {
445 #if ENABLE_FEATURE_IPV6
446                 build_ipv6_addr(local_addr, &param->localaddr.sin6);
447                 build_ipv6_addr(rem_addr, &param->remaddr.sin6);
448 #endif
449         } else {
450                 build_ipv4_addr(local_addr, &param->localaddr.sin);
451                 build_ipv4_addr(rem_addr, &param->remaddr.sin);
452         }
453         return 0;
454 }
455
456 static void print_inet_line(struct inet_params *param,
457                 const char *state_str, const char *proto, int is_connected)
458 {
459         if ((is_connected && (flags & NETSTAT_CONNECTED))
460          || (!is_connected && (flags & NETSTAT_LISTENING))
461         ) {
462                 char *l = ip_port_str(
463                                 &param->localaddr.sa, param->local_port,
464                                 proto, flags & NETSTAT_NUMERIC);
465                 char *r = ip_port_str(
466                                 &param->remaddr.sa, param->rem_port,
467                                 proto, flags & NETSTAT_NUMERIC);
468                 printf(FMT_NET_CONN_DATA,
469                         proto, param->rxq, param->txq,
470                         IF_FEATURE_NETSTAT_WIDE(G.addr_width,) l,
471                         IF_FEATURE_NETSTAT_WIDE(G.addr_width,) r,
472                         state_str);
473 #if ENABLE_FEATURE_NETSTAT_PRG
474                 if (option_mask32 & OPT_prg)
475                         printf("%."PROGNAME_WIDTH_STR"s", prg_cache_get(param->inode));
476 #endif
477                 bb_putchar('\n');
478                 free(l);
479                 free(r);
480         }
481 }
482
483 static int FAST_FUNC tcp_do_one(char *line)
484 {
485         struct inet_params param;
486
487         memset(&param, 0, sizeof(param));
488         if (scan_inet_proc_line(&param, line))
489                 return 1;
490
491         print_inet_line(&param, tcp_state[param.state], "tcp", param.rem_port);
492         return 0;
493 }
494
495 #if ENABLE_FEATURE_IPV6
496 # define NOT_NULL_ADDR(A) ( \
497         ( (A.sa.sa_family == AF_INET6) \
498           && (A.sin6.sin6_addr.s6_addr32[0] | A.sin6.sin6_addr.s6_addr32[1] | \
499               A.sin6.sin6_addr.s6_addr32[2] | A.sin6.sin6_addr.s6_addr32[3])  \
500         ) || ( \
501           (A.sa.sa_family == AF_INET) \
502           && A.sin.sin_addr.s_addr != 0 \
503         ) \
504 )
505 #else
506 # define NOT_NULL_ADDR(A) (A.sin.sin_addr.s_addr)
507 #endif
508
509 static int FAST_FUNC udp_do_one(char *line)
510 {
511         int have_remaddr;
512         const char *state_str;
513         struct inet_params param;
514
515         memset(&param, 0, sizeof(param)); /* otherwise we display garbage IPv6 scope_ids */
516         if (scan_inet_proc_line(&param, line))
517                 return 1;
518
519         state_str = "UNKNOWN";
520         switch (param.state) {
521         case TCP_ESTABLISHED:
522                 state_str = "ESTABLISHED";
523                 break;
524         case TCP_CLOSE:
525                 state_str = "";
526                 break;
527         }
528
529         have_remaddr = NOT_NULL_ADDR(param.remaddr);
530         print_inet_line(&param, state_str, "udp", have_remaddr);
531         return 0;
532 }
533
534 static int FAST_FUNC raw_do_one(char *line)
535 {
536         int have_remaddr;
537         struct inet_params param;
538
539         if (scan_inet_proc_line(&param, line))
540                 return 1;
541
542         have_remaddr = NOT_NULL_ADDR(param.remaddr);
543         print_inet_line(&param, itoa(param.state), "raw", have_remaddr);
544         return 0;
545 }
546
547 static int FAST_FUNC unix_do_one(char *line)
548 {
549         unsigned long refcnt, proto, unix_flags;
550         unsigned long inode;
551         int type, state;
552         int num, path_ofs;
553         const char *ss_proto, *ss_state, *ss_type;
554         char ss_flags[32];
555
556         /* 2.6.15 may report lines like "... @/tmp/fam-user-^@^@^@^@^@^@^@..."
557          * Other users report long lines filled by NUL bytes.
558          * (those ^@ are NUL bytes too). We see them as empty lines. */
559         if (!line[0])
560                 return 0;
561
562         path_ofs = 0; /* paranoia */
563         num = sscanf(line, "%*p: %lX %lX %lX %X %X %lu %n",
564                         &refcnt, &proto, &unix_flags, &type, &state, &inode, &path_ofs);
565         if (num < 6) {
566                 return 1; /* error */
567         }
568         if ((flags & (NETSTAT_LISTENING|NETSTAT_CONNECTED)) != (NETSTAT_LISTENING|NETSTAT_CONNECTED)) {
569                 if ((state == SS_UNCONNECTED) && (unix_flags & SO_ACCEPTCON)) {
570                         if (!(flags & NETSTAT_LISTENING))
571                                 return 0;
572                 } else {
573                         if (!(flags & NETSTAT_CONNECTED))
574                                 return 0;
575                 }
576         }
577
578         switch (proto) {
579                 case 0:
580                         ss_proto = "unix";
581                         break;
582                 default:
583                         ss_proto = "??";
584         }
585
586         switch (type) {
587                 case SOCK_STREAM:
588                         ss_type = "STREAM";
589                         break;
590                 case SOCK_DGRAM:
591                         ss_type = "DGRAM";
592                         break;
593                 case SOCK_RAW:
594                         ss_type = "RAW";
595                         break;
596                 case SOCK_RDM:
597                         ss_type = "RDM";
598                         break;
599                 case SOCK_SEQPACKET:
600                         ss_type = "SEQPACKET";
601                         break;
602                 default:
603                         ss_type = "UNKNOWN";
604         }
605
606         switch (state) {
607                 case SS_FREE:
608                         ss_state = "FREE";
609                         break;
610                 case SS_UNCONNECTED:
611                         /*
612                          * Unconnected sockets may be listening
613                          * for something.
614                          */
615                         if (unix_flags & SO_ACCEPTCON) {
616                                 ss_state = "LISTENING";
617                         } else {
618                                 ss_state = "";
619                         }
620                         break;
621                 case SS_CONNECTING:
622                         ss_state = "CONNECTING";
623                         break;
624                 case SS_CONNECTED:
625                         ss_state = "CONNECTED";
626                         break;
627                 case SS_DISCONNECTING:
628                         ss_state = "DISCONNECTING";
629                         break;
630                 default:
631                         ss_state = "UNKNOWN";
632         }
633
634         strcpy(ss_flags, "[ ");
635         if (unix_flags & SO_ACCEPTCON)
636                 strcat(ss_flags, "ACC ");
637         if (unix_flags & SO_WAITDATA)
638                 strcat(ss_flags, "W ");
639         if (unix_flags & SO_NOSPACE)
640                 strcat(ss_flags, "N ");
641         strcat(ss_flags, "]");
642
643         printf("%-5s %-6lu %-11s %-10s %-13s %6lu ",
644                 ss_proto, refcnt, ss_flags, ss_type, ss_state, inode
645                 );
646
647 #if ENABLE_FEATURE_NETSTAT_PRG
648         if (option_mask32 & OPT_prg)
649                 printf("%-"PROGNAME_WIDTH_STR"s", prg_cache_get(inode));
650 #endif
651
652         /* TODO: currently we stop at first NUL byte. Is it a problem? */
653         line += path_ofs;
654         chomp(line);
655         while (*line)
656                 fputc_printable(*line++, stdout);
657         bb_putchar('\n');
658         return 0;
659 }
660
661 static void do_info(const char *file, int FAST_FUNC (*proc)(char *))
662 {
663         int lnr;
664         FILE *procinfo;
665         char *buffer;
666
667         /* _stdin is just to save "r" param */
668         procinfo = fopen_or_warn_stdin(file);
669         if (procinfo == NULL) {
670                 return;
671         }
672         lnr = 0;
673         /* Why xmalloc_fgets_str? because it doesn't stop on NULs */
674         while ((buffer = xmalloc_fgets_str(procinfo, "\n")) != NULL) {
675                 /* line 0 is skipped */
676                 if (lnr && proc(buffer))
677                         bb_error_msg("%s: bogus data on line %d", file, lnr + 1);
678                 lnr++;
679                 free(buffer);
680         }
681         fclose(procinfo);
682 }
683
684 int netstat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
685 int netstat_main(int argc UNUSED_PARAM, char **argv)
686 {
687         unsigned opt;
688
689         INIT_G();
690
691         /* Option string must match NETSTAT_xxx constants */
692         opt = getopt32(argv, NETSTAT_OPTS);
693         if (opt & OPT_sock_listen) { // -l
694                 flags &= ~NETSTAT_CONNECTED;
695                 flags |= NETSTAT_LISTENING;
696         }
697         if (opt & OPT_sock_all) flags |= NETSTAT_LISTENING | NETSTAT_CONNECTED; // -a
698         //if (opt & OPT_extended) // -e
699         if (opt & OPT_noresolve) flags |= NETSTAT_NUMERIC; // -n
700         //if (opt & OPT_sock_tcp) // -t: NETSTAT_TCP
701         //if (opt & OPT_sock_udp) // -u: NETSTAT_UDP
702         //if (opt & OPT_sock_raw) // -w: NETSTAT_RAW
703         //if (opt & OPT_sock_unix) // -x: NETSTAT_UNIX
704 #if ENABLE_ROUTE
705         if (opt & OPT_route) { // -r
706                 bb_displayroutes(flags & NETSTAT_NUMERIC, !(opt & OPT_extended));
707                 return 0;
708         }
709 #endif
710 #if ENABLE_FEATURE_NETSTAT_WIDE
711         G.addr_width = ADDR_NORMAL_WIDTH;
712         if (opt & OPT_wide) { // -W
713                 G.addr_width = ADDR_WIDE;
714         }
715 #endif
716 #if ENABLE_FEATURE_NETSTAT_PRG
717         progname_banner = "";
718         if (opt & OPT_prg) { // -p
719                 progname_banner = PROGNAME_BANNER;
720                 prg_cache_load();
721         }
722 #endif
723
724         opt &= NETSTAT_ALLPROTO;
725         if (opt) {
726                 flags &= ~NETSTAT_ALLPROTO;
727                 flags |= opt;
728         }
729         if (flags & (NETSTAT_TCP|NETSTAT_UDP|NETSTAT_RAW)) {
730                 printf("Active Internet connections "); /* xxx */
731
732                 if ((flags & (NETSTAT_LISTENING|NETSTAT_CONNECTED)) == (NETSTAT_LISTENING|NETSTAT_CONNECTED))
733                         printf("(servers and established)");
734                 else if (flags & NETSTAT_LISTENING)
735                         printf("(only servers)");
736                 else
737                         printf("(w/o servers)");
738                 printf(FMT_NET_CONN_HEADER,
739                                 IF_FEATURE_NETSTAT_WIDE(G.addr_width,) "Local Address",
740                                 IF_FEATURE_NETSTAT_WIDE(G.addr_width,) "Foreign Address",
741                                 progname_banner
742                 );
743         }
744         if (flags & NETSTAT_TCP) {
745                 do_info("/proc/net/tcp", tcp_do_one);
746 #if ENABLE_FEATURE_IPV6
747                 do_info("/proc/net/tcp6", tcp_do_one);
748 #endif
749         }
750         if (flags & NETSTAT_UDP) {
751                 do_info("/proc/net/udp", udp_do_one);
752 #if ENABLE_FEATURE_IPV6
753                 do_info("/proc/net/udp6", udp_do_one);
754 #endif
755         }
756         if (flags & NETSTAT_RAW) {
757                 do_info("/proc/net/raw", raw_do_one);
758 #if ENABLE_FEATURE_IPV6
759                 do_info("/proc/net/raw6", raw_do_one);
760 #endif
761         }
762         if (flags & NETSTAT_UNIX) {
763                 printf("Active UNIX domain sockets ");
764                 if ((flags & (NETSTAT_LISTENING|NETSTAT_CONNECTED)) == (NETSTAT_LISTENING|NETSTAT_CONNECTED))
765                         printf("(servers and established)");
766                 else if (flags & NETSTAT_LISTENING)
767                         printf("(only servers)");
768                 else
769                         printf("(w/o servers)");
770                 printf("\nProto RefCnt Flags       Type       State         I-Node %sPath\n", progname_banner);
771                 do_info("/proc/net/unix", unix_do_one);
772         }
773         prg_cache_clear();
774         return 0;
775 }