busybox/networking/netstat.c
<<
>>
Prefs
   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
  49enum {
  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
  78enum {
  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
  92static 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
 107typedef 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
 140struct prg_node {
 141        struct prg_node *next;
 142        long inode;
 143        char name[PROGNAME_WIDTH];
 144};
 145
 146#define PRG_HASH_SIZE 211
 147
 148struct 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
 181static 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
 200static 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
 212static 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
 227static 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
 250static 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
 268static 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
 309static 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
 333static 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
 348static 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
 354static 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
 367static 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
 382struct 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
 394static 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
 427static 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
 454static 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
 480static 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
 505static 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
 518static 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
 632static 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
 655int netstat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 656int 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}
 747