config: deindent all help texts
[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         smallint 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_error_msg("can't scan /proc - are you root?");
347         else
348                 bb_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 = numeric ? xmalloc_sockaddr2dotted_noport(addr)
401                        : xmalloc_sockaddr2host_noport(addr);
402
403         host_port = xasprintf("%s:%s", host, get_sname(htons(port), proto, numeric));
404         free(host);
405         return host_port;
406 }
407
408 struct inet_params {
409         int local_port, rem_port, state, uid;
410         union {
411                 struct sockaddr     sa;
412                 struct sockaddr_in  sin;
413 #if ENABLE_FEATURE_IPV6
414                 struct sockaddr_in6 sin6;
415 #endif
416         } localaddr, remaddr;
417         unsigned long rxq, txq, inode;
418 };
419
420 static int scan_inet_proc_line(struct inet_params *param, char *line)
421 {
422         int num;
423         /* IPv6 /proc files use 32-char hex representation
424          * of IPv6 address, followed by :PORT_IN_HEX
425          */
426         char local_addr[33], rem_addr[33]; /* 32 + 1 for NUL */
427
428         num = sscanf(line,
429                         "%*d: %32[0-9A-Fa-f]:%X "
430                         "%32[0-9A-Fa-f]:%X %X "
431                         "%lX:%lX %*X:%*X "
432                         "%*X %d %*d %lu ",
433                         local_addr, &param->local_port,
434                         rem_addr, &param->rem_port, &param->state,
435                         &param->txq, &param->rxq,
436                         &param->uid, &param->inode);
437         if (num < 9) {
438                 return 1; /* error */
439         }
440
441         if (strlen(local_addr) > 8) {
442 #if ENABLE_FEATURE_IPV6
443                 build_ipv6_addr(local_addr, &param->localaddr.sin6);
444                 build_ipv6_addr(rem_addr, &param->remaddr.sin6);
445 #endif
446         } else {
447                 build_ipv4_addr(local_addr, &param->localaddr.sin);
448                 build_ipv4_addr(rem_addr, &param->remaddr.sin);
449         }
450         return 0;
451 }
452
453 static void print_inet_line(struct inet_params *param,
454                 const char *state_str, const char *proto, int is_connected)
455 {
456         if ((is_connected && (flags & NETSTAT_CONNECTED))
457          || (!is_connected && (flags & NETSTAT_LISTENING))
458         ) {
459                 char *l = ip_port_str(
460                                 &param->localaddr.sa, param->local_port,
461                                 proto, flags & NETSTAT_NUMERIC);
462                 char *r = ip_port_str(
463                                 &param->remaddr.sa, param->rem_port,
464                                 proto, flags & NETSTAT_NUMERIC);
465                 printf(FMT_NET_CONN_DATA,
466                         proto, param->rxq, param->txq,
467                         IF_FEATURE_NETSTAT_WIDE(G.addr_width,) l,
468                         IF_FEATURE_NETSTAT_WIDE(G.addr_width,) r,
469                         state_str);
470 #if ENABLE_FEATURE_NETSTAT_PRG
471                 if (option_mask32 & OPT_prg)
472                         printf("%."PROGNAME_WIDTH_STR"s", prg_cache_get(param->inode));
473 #endif
474                 bb_putchar('\n');
475                 free(l);
476                 free(r);
477         }
478 }
479
480 static int FAST_FUNC tcp_do_one(char *line)
481 {
482         struct inet_params param;
483
484         memset(&param, 0, sizeof(param));
485         if (scan_inet_proc_line(&param, line))
486                 return 1;
487
488         print_inet_line(&param, tcp_state[param.state], "tcp", param.rem_port);
489         return 0;
490 }
491
492 #if ENABLE_FEATURE_IPV6
493 # define NOT_NULL_ADDR(A) ( \
494         ( (A.sa.sa_family == AF_INET6) \
495           && (A.sin6.sin6_addr.s6_addr32[0] | A.sin6.sin6_addr.s6_addr32[1] | \
496               A.sin6.sin6_addr.s6_addr32[2] | A.sin6.sin6_addr.s6_addr32[3])  \
497         ) || ( \
498           (A.sa.sa_family == AF_INET) \
499           && A.sin.sin_addr.s_addr != 0 \
500         ) \
501 )
502 #else
503 # define NOT_NULL_ADDR(A) (A.sin.sin_addr.s_addr)
504 #endif
505
506 static int FAST_FUNC udp_do_one(char *line)
507 {
508         int have_remaddr;
509         const char *state_str;
510         struct inet_params param;
511
512         memset(&param, 0, sizeof(param)); /* otherwise we display garbage IPv6 scope_ids */
513         if (scan_inet_proc_line(&param, line))
514                 return 1;
515
516         state_str = "UNKNOWN";
517         switch (param.state) {
518         case TCP_ESTABLISHED:
519                 state_str = "ESTABLISHED";
520                 break;
521         case TCP_CLOSE:
522                 state_str = "";
523                 break;
524         }
525
526         have_remaddr = NOT_NULL_ADDR(param.remaddr);
527         print_inet_line(&param, state_str, "udp", have_remaddr);
528         return 0;
529 }
530
531 static int FAST_FUNC raw_do_one(char *line)
532 {
533         int have_remaddr;
534         struct inet_params param;
535
536         if (scan_inet_proc_line(&param, line))
537                 return 1;
538
539         have_remaddr = NOT_NULL_ADDR(param.remaddr);
540         print_inet_line(&param, itoa(param.state), "raw", have_remaddr);
541         return 0;
542 }
543
544 static int FAST_FUNC unix_do_one(char *line)
545 {
546         unsigned long refcnt, proto, unix_flags;
547         unsigned long inode;
548         int type, state;
549         int num, path_ofs;
550         const char *ss_proto, *ss_state, *ss_type;
551         char ss_flags[32];
552
553         /* 2.6.15 may report lines like "... @/tmp/fam-user-^@^@^@^@^@^@^@..."
554          * Other users report long lines filled by NUL bytes.
555          * (those ^@ are NUL bytes too). We see them as empty lines. */
556         if (!line[0])
557                 return 0;
558
559         path_ofs = 0; /* paranoia */
560         num = sscanf(line, "%*p: %lX %lX %lX %X %X %lu %n",
561                         &refcnt, &proto, &unix_flags, &type, &state, &inode, &path_ofs);
562         if (num < 6) {
563                 return 1; /* error */
564         }
565         if ((flags & (NETSTAT_LISTENING|NETSTAT_CONNECTED)) != (NETSTAT_LISTENING|NETSTAT_CONNECTED)) {
566                 if ((state == SS_UNCONNECTED) && (unix_flags & SO_ACCEPTCON)) {
567                         if (!(flags & NETSTAT_LISTENING))
568                                 return 0;
569                 } else {
570                         if (!(flags & NETSTAT_CONNECTED))
571                                 return 0;
572                 }
573         }
574
575         switch (proto) {
576                 case 0:
577                         ss_proto = "unix";
578                         break;
579                 default:
580                         ss_proto = "??";
581         }
582
583         switch (type) {
584                 case SOCK_STREAM:
585                         ss_type = "STREAM";
586                         break;
587                 case SOCK_DGRAM:
588                         ss_type = "DGRAM";
589                         break;
590                 case SOCK_RAW:
591                         ss_type = "RAW";
592                         break;
593                 case SOCK_RDM:
594                         ss_type = "RDM";
595                         break;
596                 case SOCK_SEQPACKET:
597                         ss_type = "SEQPACKET";
598                         break;
599                 default:
600                         ss_type = "UNKNOWN";
601         }
602
603         switch (state) {
604                 case SS_FREE:
605                         ss_state = "FREE";
606                         break;
607                 case SS_UNCONNECTED:
608                         /*
609                          * Unconnected sockets may be listening
610                          * for something.
611                          */
612                         if (unix_flags & SO_ACCEPTCON) {
613                                 ss_state = "LISTENING";
614                         } else {
615                                 ss_state = "";
616                         }
617                         break;
618                 case SS_CONNECTING:
619                         ss_state = "CONNECTING";
620                         break;
621                 case SS_CONNECTED:
622                         ss_state = "CONNECTED";
623                         break;
624                 case SS_DISCONNECTING:
625                         ss_state = "DISCONNECTING";
626                         break;
627                 default:
628                         ss_state = "UNKNOWN";
629         }
630
631         strcpy(ss_flags, "[ ");
632         if (unix_flags & SO_ACCEPTCON)
633                 strcat(ss_flags, "ACC ");
634         if (unix_flags & SO_WAITDATA)
635                 strcat(ss_flags, "W ");
636         if (unix_flags & SO_NOSPACE)
637                 strcat(ss_flags, "N ");
638         strcat(ss_flags, "]");
639
640         printf("%-5s %-6lu %-11s %-10s %-13s %6lu ",
641                 ss_proto, refcnt, ss_flags, ss_type, ss_state, inode
642                 );
643
644 #if ENABLE_FEATURE_NETSTAT_PRG
645         if (option_mask32 & OPT_prg)
646                 printf("%-"PROGNAME_WIDTH_STR"s", prg_cache_get(inode));
647 #endif
648
649         /* TODO: currently we stop at first NUL byte. Is it a problem? */
650         line += path_ofs;
651         chomp(line);
652         while (*line)
653                 fputc_printable(*line++, stdout);
654         bb_putchar('\n');
655         return 0;
656 }
657
658 static void do_info(const char *file, int FAST_FUNC (*proc)(char *))
659 {
660         int lnr;
661         FILE *procinfo;
662         char *buffer;
663
664         /* _stdin is just to save "r" param */
665         procinfo = fopen_or_warn_stdin(file);
666         if (procinfo == NULL) {
667                 return;
668         }
669         lnr = 0;
670         /* Why xmalloc_fgets_str? because it doesn't stop on NULs */
671         while ((buffer = xmalloc_fgets_str(procinfo, "\n")) != NULL) {
672                 /* line 0 is skipped */
673                 if (lnr && proc(buffer))
674                         bb_error_msg("%s: bogus data on line %d", file, lnr + 1);
675                 lnr++;
676                 free(buffer);
677         }
678         fclose(procinfo);
679 }
680
681 int netstat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
682 int netstat_main(int argc UNUSED_PARAM, char **argv)
683 {
684         unsigned opt;
685
686         INIT_G();
687
688         /* Option string must match NETSTAT_xxx constants */
689         opt = getopt32(argv, NETSTAT_OPTS);
690         if (opt & OPT_sock_listen) { // -l
691                 flags &= ~NETSTAT_CONNECTED;
692                 flags |= NETSTAT_LISTENING;
693         }
694         if (opt & OPT_sock_all) flags |= NETSTAT_LISTENING | NETSTAT_CONNECTED; // -a
695         //if (opt & OPT_extended) // -e
696         if (opt & OPT_noresolve) flags |= NETSTAT_NUMERIC; // -n
697         //if (opt & OPT_sock_tcp) // -t: NETSTAT_TCP
698         //if (opt & OPT_sock_udp) // -u: NETSTAT_UDP
699         //if (opt & OPT_sock_raw) // -w: NETSTAT_RAW
700         //if (opt & OPT_sock_unix) // -x: NETSTAT_UNIX
701 #if ENABLE_ROUTE
702         if (opt & OPT_route) { // -r
703                 bb_displayroutes(flags & NETSTAT_NUMERIC, !(opt & OPT_extended));
704                 return 0;
705         }
706 #endif
707 #if ENABLE_FEATURE_NETSTAT_WIDE
708         G.addr_width = ADDR_NORMAL_WIDTH;
709         if (opt & OPT_wide) { // -W
710                 G.addr_width = ADDR_WIDE;
711         }
712 #endif
713 #if ENABLE_FEATURE_NETSTAT_PRG
714         progname_banner = "";
715         if (opt & OPT_prg) { // -p
716                 progname_banner = PROGNAME_BANNER;
717                 prg_cache_load();
718         }
719 #endif
720
721         opt &= NETSTAT_ALLPROTO;
722         if (opt) {
723                 flags &= ~NETSTAT_ALLPROTO;
724                 flags |= opt;
725         }
726         if (flags & (NETSTAT_TCP|NETSTAT_UDP|NETSTAT_RAW)) {
727                 printf("Active Internet connections "); /* xxx */
728
729                 if ((flags & (NETSTAT_LISTENING|NETSTAT_CONNECTED)) == (NETSTAT_LISTENING|NETSTAT_CONNECTED))
730                         printf("(servers and established)");
731                 else if (flags & NETSTAT_LISTENING)
732                         printf("(only servers)");
733                 else
734                         printf("(w/o servers)");
735                 printf(FMT_NET_CONN_HEADER,
736                                 IF_FEATURE_NETSTAT_WIDE(G.addr_width,) "Local Address",
737                                 IF_FEATURE_NETSTAT_WIDE(G.addr_width,) "Foreign Address",
738                                 progname_banner
739                 );
740         }
741         if (flags & NETSTAT_TCP) {
742                 do_info("/proc/net/tcp", tcp_do_one);
743 #if ENABLE_FEATURE_IPV6
744                 do_info("/proc/net/tcp6", tcp_do_one);
745 #endif
746         }
747         if (flags & NETSTAT_UDP) {
748                 do_info("/proc/net/udp", udp_do_one);
749 #if ENABLE_FEATURE_IPV6
750                 do_info("/proc/net/udp6", udp_do_one);
751 #endif
752         }
753         if (flags & NETSTAT_RAW) {
754                 do_info("/proc/net/raw", raw_do_one);
755 #if ENABLE_FEATURE_IPV6
756                 do_info("/proc/net/raw6", raw_do_one);
757 #endif
758         }
759         if (flags & NETSTAT_UNIX) {
760                 printf("Active UNIX domain sockets ");
761                 if ((flags & (NETSTAT_LISTENING|NETSTAT_CONNECTED)) == (NETSTAT_LISTENING|NETSTAT_CONNECTED))
762                         printf("(servers and established)");
763                 else if (flags & NETSTAT_LISTENING)
764                         printf("(only servers)");
765                 else
766                         printf("(w/o servers)");
767                 printf("\nProto RefCnt Flags       Type       State         I-Node %sPath\n", progname_banner);
768                 do_info("/proc/net/unix", unix_do_one);
769         }
770         prg_cache_clear();
771         return 0;
772 }