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