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