root/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2021 Joyent, Inc.
 * Copyright 2023 RackTop Systems, Inc.
 * Copyright 2025 Oxide Computer Company
 */

#include <stdio.h>
#include <unistd.h>
#include <stropts.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdarg.h>
#include <setjmp.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/mman.h>
#include <assert.h>
#include <sys/sysmacros.h>

#include <sys/socket.h>
#include <sys/pfmod.h>
#include <net/if.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netdb.h>

#include "snoop.h"

static int snaplen;

/* Global error recovery variables */
sigjmp_buf jmp_env, ojmp_env;           /* error recovery jmp buf */
int snoop_nrecover;                     /* number of recoveries on curr pkt */
int quitting;                           /* user termination flag */

static struct snoop_handler *snoop_hp;          /* global alarm handler head */
static struct snoop_handler *snoop_tp;          /* global alarm handler tail */
static time_t snoop_nalarm;                     /* time of next alarm */

/* protected interpreter output areas */
#define MAXSUM          8
#define REDZONE         64
static char *sumline[MAXSUM];
static char *detail_line;
static char *line;
static char *encap;

static int audio;
int maxcount;   /* maximum no of packets to capture */
int count;      /* count of packets captured */
static int sumcount;
int x_offset = -1;
int x_length = 0x7fffffff;
FILE *namefile;
boolean_t Pflg;
boolean_t Iflg;
boolean_t fflg;
boolean_t qflg;
boolean_t rflg;
#ifdef  DEBUG
boolean_t zflg;
#endif
struct Pf_ext_packetfilt pf;

static int vlanid = 0;

static void usage(void);
static void snoop_sigrecover(int sig, siginfo_t *info, void *p);
static char *protmalloc(size_t);
static void resetperm(void);

static char *
process_ocapfile(const char *arg, size_t *nfiles, off_t *limit)
{
        char *ptr = strdup(arg);
        char *name, *data;
        const char *errstr;
        long long n, m;
        int i;

        name = strsep(&ptr, ":");
        data = strsep(&ptr, ":");
        if (data == NULL || ptr == NULL)
                usage();

        n = strtonum(data, 1, 100, &errstr);
        if (errstr != NULL) {
                printf("%s: value %s: %s\n", __func__, data, errstr);
                usage();
        }
        *nfiles = n;
        m = 1;
        i = strlen(ptr);
        if (i > 0)
                i--;

        switch (ptr[i]) {
        case 'k':
                m = 1024;
                ptr[i] = '\0';
                break;
        case 'm':
                m = 1024 * 1024;
                ptr[i] = '\0';
                break;
        case 'g':
                m = 1024 * 1024 * 1024;
                ptr[i] = '\0';
                break;
        }
        n = strtonum(ptr, 1, MAXOFF_T, &errstr);
        if (errstr != NULL) {
                printf("%s: value %s: %s\n", __func__, ptr, errstr);
                usage();
        }
        *limit = n * m;
        return (name);
}

int
main(int argc, char **argv)
{
        int c;
        int filter = 0;
        int flags = F_SUM;
        struct Pf_ext_packetfilt *fp = NULL;
        char *icapfile = NULL;
        char *ocapfile = NULL;
        boolean_t nflg = B_FALSE;
        boolean_t Nflg = B_FALSE;
        int Cflg = 0;
        boolean_t Qflg = B_FALSE;
        boolean_t Uflg = B_FALSE;
        int first = 1;
        int last  = 0x7fffffff;
        boolean_t use_kern_pf;
        char *p, *p2;
        char names[MAXPATHLEN + 1];
        char self[MAXHOSTNAMELEN + 1];
        char *argstr = NULL;
        void (*proc)();
        char *audiodev;
        int ret;
        struct sigaction sigact;
        stack_t sigstk;
        char *output_area;
        int nbytes;
        char *datalink = NULL;
        dlpi_handle_t dh;
        size_t nfiles = 0;
        off_t limit = 0;
        int direction = DIR_INOUT;

        names[0] = '\0';
        /*
         * Global error recovery: Prepare for interpreter failures
         * with corrupted packets or confused interpreters.
         * Allocate protected output and stack areas, with generous
         * red-zones.
         */
        nbytes = (MAXSUM + 3) * (MAXLINE + REDZONE);
        output_area = protmalloc(nbytes);
        if (output_area == NULL) {
                perror("Warning: mmap");
                exit(1);
        }

        /* Allocate protected output areas */
        for (ret = 0; ret < MAXSUM; ret++) {
                sumline[ret] = (char *)output_area;
                output_area += (MAXLINE + REDZONE);
        }
        detail_line = output_area;
        output_area += MAXLINE + REDZONE;
        line = output_area;
        output_area += MAXLINE + REDZONE;
        encap = output_area;
        output_area += MAXLINE + REDZONE;

        /* Initialize an alternate signal stack to increase robustness */
        if ((sigstk.ss_sp = (char *)malloc(SIGSTKSZ+REDZONE)) == NULL) {
                perror("Warning: malloc");
                exit(1);
        }
        sigstk.ss_size = SIGSTKSZ;
        sigstk.ss_flags = 0;
        if (sigaltstack(&sigstk, (stack_t *)NULL) < 0) {
                perror("Warning: sigaltstack");
                exit(1);
        }

        /* Initialize a master signal handler */
        sigact.sa_handler = NULL;
        sigact.sa_sigaction = snoop_sigrecover;
        (void) sigemptyset(&sigact.sa_mask);
        sigact.sa_flags = SA_ONSTACK|SA_SIGINFO;

        /* Register master signal handler */
        if (sigaction(SIGHUP, &sigact, (struct sigaction *)NULL) < 0) {
                perror("Warning: sigaction");
                exit(1);
        }
        if (sigaction(SIGINT, &sigact, (struct sigaction *)NULL) < 0) {
                perror("Warning: sigaction");
                exit(1);
        }
        if (sigaction(SIGQUIT, &sigact, (struct sigaction *)NULL) < 0) {
                perror("Warning: sigaction");
                exit(1);
        }
        if (sigaction(SIGILL, &sigact, (struct sigaction *)NULL) < 0) {
                perror("Warning: sigaction");
                exit(1);
        }
        if (sigaction(SIGTRAP, &sigact, (struct sigaction *)NULL) < 0) {
                perror("Warning: sigaction");
                exit(1);
        }
        if (sigaction(SIGIOT, &sigact, (struct sigaction *)NULL) < 0) {
                perror("Warning: sigaction");
                exit(1);
        }
        if (sigaction(SIGEMT, &sigact, (struct sigaction *)NULL) < 0) {
                perror("Warning: sigaction");
                exit(1);
        }
        if (sigaction(SIGFPE, &sigact, (struct sigaction *)NULL) < 0) {
                perror("Warning: sigaction");
                exit(1);
        }
        if (sigaction(SIGBUS, &sigact, (struct sigaction *)NULL) < 0) {
                perror("Warning: sigaction");
                exit(1);
        }
        if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) < 0) {
                perror("Warning: sigaction");
                exit(1);
        }
        if (sigaction(SIGSYS, &sigact, (struct sigaction *)NULL) < 0) {
                perror("Warning: sigaction");
                exit(1);
        }
        if (sigaction(SIGALRM, &sigact, (struct sigaction *)NULL) < 0) {
                perror("Warning: sigaction");
                exit(1);
        }
        if (sigaction(SIGTERM, &sigact, (struct sigaction *)NULL) < 0) {
                perror("Warning: sigaction");
                exit(1);
        }

        /* Prepare for failure during program initialization/exit */
        if (sigsetjmp(jmp_env, 1)) {
                exit(1);
        }
        (void) setvbuf(stdout, NULL, _IOLBF, BUFSIZ);

        while ((c = getopt(argc, argv,
            "at:CPDSi:O:o:Nn:s:d:I:vVp:fc:x:U?rqQ:z")) != EOF) {
                switch (c) {
                case 'a':
                        audiodev = getenv("AUDIODEV");
                        if (audiodev == NULL)
                                audiodev = "/dev/audio";
                        audio = open(audiodev, O_WRONLY);
                        if (audio < 0) {
                                pr_err("Audio device %s: %m",
                                    audiodev);
                                exit(1);
                        }
                        break;
                case 't':
                        flags |= F_TIME;
                        switch (*optarg) {
                        case 'r':       flags |= F_RTIME; break;
                        case 'a':       flags |= F_ATIME; break;
                        case 'd':       break;
                        default:        usage();
                        }
                        break;
                case 'I':
                        if (datalink != NULL)
                                usage();
                        Iflg = B_TRUE;
                        datalink = optarg;
                        break;
                case 'P':
                        Pflg = B_TRUE;
                        break;
                case 'D':
                        flags |= F_DROPS;
                        break;
                case 'S':
                        flags |= F_LEN;
                        break;
                case 'i':
                        icapfile = optarg;
                        break;
                case 'O':
                        if (ocapfile != NULL)
                                usage();
                        ocapfile = process_ocapfile(optarg, &nfiles, &limit);
                        break;
                case 'o':
                        if (ocapfile != NULL)
                                usage();
                        ocapfile = optarg;
                        break;
                case 'N':
                        Nflg = B_TRUE;
                        break;
                case 'n':
                        nflg = B_TRUE;
                        (void) strlcpy(names, optarg, MAXPATHLEN);
                        break;
                case 's':
                        snaplen = atoi(optarg);
                        break;
                case 'd':
                        if (Iflg)
                                usage();
                        datalink = optarg;
                        break;
                case 'v':
                        flags &= ~(F_SUM);
                        flags |= F_DTAIL;
                        break;
                case 'V':
                        flags |= F_ALLSUM;
                        break;
                case 'p':
                        p = optarg;
                        p2 = strpbrk(p, ",:-");
                        if (p2 == NULL) {
                                first = last = atoi(p);
                        } else {
                                *p2++ = '\0';
                                first = atoi(p);
                                last = atoi(p2);
                        }
                        break;
                case 'f':
                        fflg = B_TRUE;
                        break;
                case 'x':
                        p = optarg;
                        p2 = strpbrk(p, ",:-");
                        if (p2 == NULL) {
                                x_offset = atoi(p);
                                x_length = -1;
                        } else {
                                *p2++ = '\0';
                                x_offset = atoi(p);
                                x_length = atoi(p2);
                        }
                        break;
                case 'c':
                        maxcount = atoi(optarg);
                        break;
                case 'C':
                        Cflg = B_TRUE;
                        break;
                case 'q':
                        qflg = B_TRUE;
                        break;
                case 'Q':
                        Qflg = B_TRUE;
                        if (strcasecmp("inout", optarg) == 0)
                                direction = DIR_INOUT;
                        else if (strcasecmp("in", optarg) == 0)
                                direction = DIR_IN;
                        else if (strcasecmp("out", optarg) == 0)
                                direction = DIR_OUT;
                        else
                                usage();
                        break;
                case 'r':
                        rflg = B_TRUE;
                        break;
                case 'U':
                        Uflg = B_TRUE;
                        break;
#ifdef  DEBUG
                case 'z':
                        zflg = B_TRUE;
                        break;
#endif  /* DEBUG */
                case '?':
                default:
                        usage();
                }
        }

        if (argc > optind)
                argstr = (char *)concat_args(&argv[optind], argc - optind);

        /*
         * Need to know before we decide on filtering method some things
         * about the interface.  So, go ahead and do part of the initialization
         * now so we have that data.  Note that if no datalink is specified,
         * open_datalink() selects one and returns it.  In an ideal world,
         * it might be nice if the "correct" interface for the filter
         * requested was chosen, but that's too hard.
         */
        if (!icapfile) {
                use_kern_pf = open_datalink(&dh, datalink);
        } else {
                if (Qflg) {
                        pr_err("cannot specify direction (-Q) when "
                            "reading from a capture file (-i)");
                }

                use_kern_pf = B_FALSE;
                cap_open_read(icapfile);

                if (!nflg) {
                        names[0] = '\0';
                        (void) strlcpy(names, icapfile, MAXPATHLEN);
                        (void) strlcat(names, ".names", MAXPATHLEN);
                }
        }

        if (Uflg)
                use_kern_pf = B_FALSE;

        /* attempt to read .names file if it exists before filtering */
        if ((!Nflg) && names[0] != '\0') {
                if (access(names, F_OK) == 0) {
                        load_names(names);
                } else if (nflg) {
                        (void) fprintf(stderr, "%s not found\n", names);
                        exit(1);
                }
        }

        if (argstr) {
                if (use_kern_pf) {
                        ret = pf_compile(argstr, Cflg);
                        switch (ret) {
                        case 0:
                                filter++;
                                compile(argstr, Cflg);
                                break;
                        case 1:
                                fp = &pf;
                                break;
                        case 2:
                                fp = &pf;
                                filter++;
                                break;
                        }
                } else {
                        filter++;
                        compile(argstr, Cflg);
                }

                if (Cflg)
                        exit(0);
        }

        if (flags & F_SUM)
                flags |= F_WHO;

        /*
         * If the -o flag is set then capture packets
         * directly to a file.  Don't attempt to
         * interpret them on the fly (F_NOW).
         * Note: capture to file is much less likely
         * to drop packets since we don't spend cpu
         * cycles running through the interpreters
         * and possibly hanging in address-to-name
         * mappings through the name service.
         */
        if (ocapfile) {
                if (nfiles > 1)
                        cap_open_wr_multi(ocapfile, nfiles, limit);
                else
                        cap_open_write(ocapfile);
                proc = cap_write;
        } else {
                flags |= F_NOW;
                proc = process_pkt;
        }


        /*
         * If the -i flag is set then get packets from
         * the log file which has been previously captured
         * with the -o option.
         */
        if (icapfile) {
                names[0] = '\0';
                (void) strlcpy(names, icapfile, MAXPATHLEN);
                (void) strlcat(names, ".names", MAXPATHLEN);

                if (Nflg) {
                        namefile = fopen(names, "w");
                        if (namefile == NULL) {
                                perror(names);
                                exit(1);
                        }
                        flags = 0;
                        (void) fprintf(stderr,
                            "Creating name file %s\n", names);
                }

                if (flags & F_DTAIL)
                        flags = F_DTAIL;
                else
                        flags |= F_NUM | F_TIME;

                resetperm();
                cap_read(first, last, filter, proc, flags);

                if (Nflg)
                        (void) fclose(namefile);

        } else {
                const int chunksize = 8 * 8192;
                struct timeval timeout;

                /*
                 * If listening to packets on audio
                 * then set the buffer timeout down
                 * to 1/10 sec.  A higher value
                 * makes the audio "bursty".
                 */
                if (audio) {
                        timeout.tv_sec = 0;
                        timeout.tv_usec = 100000;
                } else {
                        timeout.tv_sec = 1;
                        timeout.tv_usec = 0;
                }

                init_datalink(dh, snaplen, chunksize, &timeout, fp, direction);
                if (! qflg && ocapfile)
                        show_count();
                resetperm();
                net_read(dh, chunksize, filter, proc, flags);
                dlpi_close(dh);

                if (!(flags & F_NOW))
                        (void) printf("\n");
        }

        if (ocapfile)
                cap_close();

        return (0);
}

static int tone[] = {
0x076113, 0x153333, 0x147317, 0x144311, 0x147315, 0x050353, 0x037103, 0x051106,
0x157155, 0x142723, 0x133273, 0x134664, 0x051712, 0x024465, 0x026447, 0x072473,
0x136715, 0x126257, 0x135256, 0x047344, 0x034476, 0x027464, 0x036062, 0x133334,
0x127256, 0x130660, 0x136262, 0x040724, 0x016446, 0x025437, 0x137171, 0x127672,
0x124655, 0x134654, 0x032741, 0x021447, 0x037450, 0x125675, 0x127650, 0x077277,
0x046514, 0x036077, 0x035471, 0x147131, 0x136272, 0x162720, 0x166151, 0x037527,
};

/*
 * Make a sound on /dev/audio according to the length of the packet.  The
 * tone data was ripped from /usr/share/audio/samples/au/bark.au.  The
 * amount of waveform used is a function of packet length e.g.  a series
 * of small packets is heard as clicks, whereas a series of NFS packets in
 * an 8k read sounds like a "WHAAAARP".
 */
void
click(int len)
{
        len /= 8;
        len = len ? len : 4;

        if (audio) {
                (void) write(audio, tone, len);
        }
}

/* Display a count of packets */
void
show_count()
{
        static int prev = -1;

        if (count == prev)
                return;

        prev = count;
        (void) fprintf(stderr, "\r%d ", count);
}

#define ENCAP_LEN       16      /* Hold "(NN encap)" */

/*
 * Display data that's external to the packet.
 * This constitutes the first half of the summary
 * line display.
 */
void
show_pktinfo(int flags, int num, char *src, char *dst, struct timeval *ptvp,
    struct timeval *tvp, int drops, int len)
{
        struct tm *tm;
        static struct timeval tvp0;
        int sec, usec;
        char *lp = line;
        int i, start;

        if (flags & F_NUM) {
                (void) sprintf(lp, "%3d ", num);
                lp += strlen(lp);
        }
        tm = localtime(&tvp->tv_sec);

        if (flags & F_TIME) {
                if (flags & F_ATIME) {
                        (void) sprintf(lp, "%02d:%02d:%02d.%05d ",
                            tm->tm_hour, tm->tm_min, tm->tm_sec,
                            (int)tvp->tv_usec / 10);
                        lp += strlen(lp);
                } else {
                        if (flags & F_RTIME) {
                                if (tvp0.tv_sec == 0) {
                                        tvp0.tv_sec = tvp->tv_sec;
                                        tvp0.tv_usec = tvp->tv_usec;
                                }
                                ptvp = &tvp0;
                        }
                        sec  = tvp->tv_sec  - ptvp->tv_sec;
                        usec = tvp->tv_usec - ptvp->tv_usec;
                        if (usec < 0) {
                                usec += 1000000;
                                sec  -= 1;
                        }
                        (void) sprintf(lp, "%3d.%05d ", sec, usec / 10);
                        lp += strlen(lp);
                }
        }

        if ((flags & F_SUM) && !(flags & F_ALLSUM) && (vlanid != 0)) {
                (void) snprintf(lp, MAXLINE, "VLAN#%i: ", vlanid);
                lp += strlen(lp);
        }

        if (flags & F_WHO) {
                (void) sprintf(lp, "%12s -> %-12s ", src, dst);
                lp += strlen(lp);
        }

        if (flags & F_DROPS) {
                (void) sprintf(lp, "drops: %d ", drops);
                lp += strlen(lp);
        }

        if (flags & F_LEN) {
                (void) sprintf(lp, "length: %4d  ", len);
                lp += strlen(lp);
        }

        if (flags & F_SUM) {
                if (flags & F_ALLSUM)
                        (void) printf("________________________________\n");

                start = flags & F_ALLSUM ? 0 : sumcount - 1;
                (void) sprintf(encap, "  (%d encap)", total_encap_levels - 1);
                (void) printf("%s%s%s\n", line, sumline[start],
                    ((flags & F_ALLSUM) || (total_encap_levels == 1)) ? "" :
                    encap);

                for (i = start + 1; i < sumcount; i++)
                        (void) printf("%s%s\n", line, sumline[i]);

                sumcount = 0;
        }

        if (flags & F_DTAIL) {
                (void) printf("%s\n\n", detail_line);
                detail_line[0] = '\0';
        }
}

/*
 * The following three routines are called back
 * from the interpreters to display their stuff.
 * The theory is that when snoop becomes a window
 * based tool we can just supply a new version of
 * get_sum_line and get_detail_line and not have
 * to touch the interpreters at all.
 */
char *
get_sum_line()
{
        int tsumcount = sumcount;

        if (sumcount >= MAXSUM) {
                sumcount = 0;                   /* error recovery */
                pr_err(
                    "get_sum_line: sumline overflow (sumcount=%d, MAXSUM=%d)\n",
                    tsumcount, MAXSUM);
        }

        sumline[sumcount][0] = '\0';
        return (sumline[sumcount++]);
}

/*ARGSUSED*/
char *
get_detail_line(int off, int len)
{
        if (detail_line[0]) {
                (void) printf("%s\n", detail_line);
                detail_line[0] = '\0';
        }
        return (detail_line);
}

/*
 * This function exists to make sure that VLAN information is
 * prepended to summary lines displayed.  The problem this function
 * solves is how to display VLAN information while in summary mode.
 * Each interpretor uses the get_sum_line and get_detail_line functions
 * to get a character buffer to display information to the user.
 * get_sum_line is the important one here.  Each call to get_sum_line
 * gets a buffer which stores one line of information.  In summary mode,
 * the last line generated is the line printed.  Instead of changing each
 * interpreter to add VLAN information to the summary line, the ethernet
 * interpreter changes to call this function and set an ID.  If the ID is not
 * zero and snoop is in default summary mode, snoop displays the
 * VLAN information at the beginning of the output line.  Otherwise,
 * no VLAN information is displayed.
 */
void
set_vlan_id(int id)
{
        vlanid = id;
}

/*
 * Print an error.
 * Works like printf (fmt string and variable args)
 * except that it will substitute an error message
 * for a "%m" string (like syslog) and it calls
 * long_jump - it doesn't return to where it was
 * called from - it goes to the last setjmp().
 */
/* VARARGS1 */
void
pr_err(const char *fmt, ...)
{
        va_list ap;
        char buf[1024], *p2;
        const char *p1;

        (void) strcpy(buf, "snoop: ");
        p2 = buf + strlen(buf);

        /*
         * Note that we terminate the buffer with '\n' and '\0'.
         */
        for (p1 = fmt; *p1 != '\0' && p2 < buf + sizeof (buf) - 2; p1++) {
                if (*p1 == '%' && *(p1+1) == 'm') {
                        const char *errstr;

                        if ((errstr = strerror(errno)) != NULL) {
                                *p2 = '\0';
                                (void) strlcat(buf, errstr, sizeof (buf));
                                p2 += strlen(p2);
                        }
                        p1++;
                } else {
                        *p2++ = *p1;
                }
        }
        if (p2 > buf && *(p2-1) != '\n')
                *p2++ = '\n';
        *p2 = '\0';

        va_start(ap, fmt);
        /* LINTED: E_SEC_PRINTF_VAR_FMT */
        (void) vfprintf(stderr, buf, ap);
        va_end(ap);
        snoop_sigrecover(-1, NULL, NULL);       /* global error recovery */
}

/*
 * Store a copy of linkname associated with the DLPI handle.
 * Save errno before closing the dlpi handle so that the
 * correct error value is used if 'err' is a system error.
 */
void
pr_errdlpi(dlpi_handle_t dh, const char *cmd, int err)
{
        int save_errno = errno;
        char linkname[DLPI_LINKNAME_MAX];

        (void) strlcpy(linkname, dlpi_linkname(dh), sizeof (linkname));

        dlpi_close(dh);
        errno = save_errno;

        pr_err("%s on \"%s\": %s", cmd, linkname, dlpi_strerror(err));
}

/*
 * Ye olde usage proc
 * PLEASE keep this up to date!
 * Naive users *love* this stuff.
 */
static void
usage(void)
{
        (void) fprintf(stderr, "\nUsage:  snoop\n");
        (void) fprintf(stderr,
        "\t[ -a ]                       # Listen to packets on audio\n");
        (void) fprintf(stderr,
        "\t[ -d link ]                  # Listen on named link\n");
        (void) fprintf(stderr,
        "\t[ -s snaplen ]               # Truncate packets\n");
        (void) fprintf(stderr,
        "\t[ -I IP interface ]          # Listen on named IP interface\n");
        (void) fprintf(stderr,
        "\t[ -c count ]                 # Quit after count packets\n");
        (void) fprintf(stderr,
        "\t[ -P ]                       # Turn OFF promiscuous mode\n");
        (void) fprintf(stderr,
        "\t[ -D ]                       # Report dropped packets\n");
        (void) fprintf(stderr,
        "\t[ -S ]                       # Report packet size\n");
        (void) fprintf(stderr,
        "\t[ -i file ]                  # Read previously captured packets\n");
        (void) fprintf(stderr,
        "\t[ -O prefix:count:size ]     # Capture packets in files\n");
        (void) fprintf(stderr,
        "\t[ -o file ]                  # Capture packets in file\n");
        (void) fprintf(stderr,
        "\t[ -n file ]                  # Load addr-to-name table from file\n");
        (void) fprintf(stderr,
        "\t[ -N ]                       # Create addr-to-name table\n");
        (void) fprintf(stderr,
        "\t[ -t  r|a|d ]                # Time: Relative, Absolute or Delta\n");
        (void) fprintf(stderr,
        "\t[ -v ]                       # Verbose packet display\n");
        (void) fprintf(stderr,
        "\t[ -V ]                       # Show all summary lines\n");
        (void) fprintf(stderr,
        "\t[ -p first[,last] ]          # Select packet(s) to display\n");
        (void) fprintf(stderr,
        "\t[ -x offset[,length] ]       # Hex dump from offset for length\n");
        (void) fprintf(stderr,
        "\t[ -C ]                       # Print packet filter code\n");
        (void) fprintf(stderr,
        "\t[ -q ]                       # Suppress printing packet count\n");
        (void) fprintf(stderr,
        "\t[ -Q inout|in|out ]          # Set packet capture direction\n");
        (void) fprintf(stderr,
        "\t[ -r ]                       # Do not resolve address to name\n");
        (void) fprintf(stderr,
        "\n\t[ filter expression ]\n");
        (void) fprintf(stderr, "\nExample:\n");
        (void) fprintf(stderr, "\tsnoop -o saved  host fred\n\n");
        (void) fprintf(stderr, "\tsnoop -i saved -tr -v -p19\n");
        exit(1);
}

/*
 * sdefault: default global alarm handler. Causes the current packet
 * to be skipped.
 */
static void
sdefault(void)
{
        snoop_nrecover = SNOOP_MAXRECOVER;
}

/*
 * snoop_alarm: register or unregister an alarm handler to be called after
 * s_sec seconds. Because snoop wasn't written to tolerate random signal
 * delivery, periodic SIGALRM delivery (or SA_RESTART) cannot be used.
 *
 * s_sec argument of 0 seconds unregisters the handler.
 * s_handler argument of NULL registers default handler sdefault(), or
 * unregisters all signal handlers (for error recovery).
 *
 * Variables must be volatile to force the compiler to not optimize
 * out the signal blocking.
 */
/*ARGSUSED*/
int
snoop_alarm(int s_sec, void (*s_handler)())
{
        volatile time_t now;
        volatile time_t nalarm = 0;
        volatile struct snoop_handler *sh = NULL;
        volatile struct snoop_handler *hp, *tp, *next;
        volatile sigset_t s_mask;
        volatile int ret = -1;

        (void) sigemptyset((sigset_t *)&s_mask);
        (void) sigaddset((sigset_t *)&s_mask, SIGALRM);
        if (s_sec < 0)
                return (-1);

        /* register an alarm handler */
        now = time(NULL);
        if (s_sec) {
                sh = malloc(sizeof (struct snoop_handler));
                sh->s_time = now + s_sec;
                if (s_handler == NULL)
                        s_handler = sdefault;
                sh->s_handler = s_handler;
                sh->s_next = NULL;
                (void) sigprocmask(SIG_BLOCK, (sigset_t *)&s_mask, NULL);
                if (snoop_hp == NULL) {
                        snoop_hp = snoop_tp = (struct snoop_handler *)sh;

                        snoop_nalarm = sh->s_time;
                        (void) alarm(sh->s_time - now);
                } else {
                        snoop_tp->s_next = (struct snoop_handler *)sh;
                        snoop_tp = (struct snoop_handler *)sh;

                        if (sh->s_time < snoop_nalarm) {
                                snoop_nalarm = sh->s_time;
                                (void) alarm(sh->s_time - now);
                        }
                }
                (void) sigprocmask(SIG_UNBLOCK, (sigset_t *)&s_mask, NULL);

                return (0);
        }

        /* unregister an alarm handler */
        (void) sigprocmask(SIG_BLOCK, (sigset_t *)&s_mask, NULL);
        tp = (struct snoop_handler *)&snoop_hp;
        for (hp = snoop_hp; hp; hp = next) {
                next = hp->s_next;
                if (s_handler == NULL || hp->s_handler == s_handler) {
                        ret = 0;
                        tp->s_next = hp->s_next;
                        if (snoop_tp == hp) {
                                if (tp == (struct snoop_handler *)&snoop_hp)
                                        snoop_tp = NULL;
                                else
                                        snoop_tp = (struct snoop_handler *)tp;
                        }
                        free((void *)hp);
                } else {
                        if (nalarm == 0 || nalarm > hp->s_time)
                                nalarm = now < hp->s_time ? hp->s_time :
                                    now + 1;
                        tp = hp;
                }
        }
        /*
         * Stop or adjust timer
         */
        if (snoop_hp == NULL) {
                snoop_nalarm = 0;
                (void) alarm(0);
        } else if (nalarm > 0 && nalarm < snoop_nalarm) {
                snoop_nalarm = nalarm;
                (void) alarm(nalarm - now);
        }

        (void) sigprocmask(SIG_UNBLOCK, (sigset_t *)&s_mask, NULL);
        return (ret);
}

/*
 * snoop_recover: reset snoop's output area, and any internal variables,
 * to allow continuation.
 * XXX: make this an interface such that each interpreter can
 * register a reset routine.
 */
void
snoop_recover(void)
{
        int i;

        /* Error recovery: reset output_area and associated variables */
        for (i = 0; i < MAXSUM; i++)
                sumline[i][0] = '\0';
        detail_line[0] = '\0';
        line[0] = '\0';
        encap[0] = '\0';
        sumcount = 0;

        /* stacking/unstacking cannot be relied upon */
        encap_levels = 0;
        total_encap_levels = 1;

        /* remove any pending timeouts */
        (void) snoop_alarm(0, NULL);
}

/*
 * snoop_sigrecover: global sigaction routine to manage recovery
 * from catastrophic interpreter failures while interpreting
 * corrupt trace files/packets. SIGALRM timeouts, program errors,
 * and user termination are all handled. In the case of a corrupt
 * packet or confused interpreter, the packet will be skipped, and
 * execution will continue in scan().
 *
 * Global alarm handling (see snoop_alarm()) is managed here.
 *
 * Variables must be volatile to force the compiler to not optimize
 * out the signal blocking.
 */
/*ARGSUSED*/
static void
snoop_sigrecover(int sig, siginfo_t *info, void *p)
{
        volatile time_t now;
        volatile time_t nalarm = 0;
        volatile struct snoop_handler *hp;

        /*
         * Invoke any registered alarms. This involves first calculating
         * the time for the next alarm, setting it up, then progressing
         * through handler invocations. Note that since handlers may
         * use siglongjmp(), in the worst case handlers may be serviced
         * at a later time.
         */
        if (sig == SIGALRM) {
                now = time(NULL);
                /* Calculate next alarm time */
                for (hp = snoop_hp; hp; hp = hp->s_next) {
                        if (hp->s_time) {
                                if ((hp->s_time - now) > 0) {
                                        if (nalarm == 0 || nalarm > hp->s_time)
                                                nalarm = now < hp->s_time ?
                                                    hp->s_time : now + 1;
                                }
                        }
                }
                /* Setup next alarm */
                if (nalarm) {
                        snoop_nalarm = nalarm;
                        (void) alarm(nalarm - now);
                } else {
                        snoop_nalarm = 0;
                }

                /* Invoke alarm handlers (may not return) */
                for (hp = snoop_hp; hp; hp = hp->s_next) {
                        if (hp->s_time) {
                                if ((now - hp->s_time) >= 0) {
                                        hp->s_time = 0; /* only invoke once */
                                        if (hp->s_handler)
                                                hp->s_handler();
                                }
                        }
                }
        } else {
                snoop_nrecover++;
        }

        /*
         * Exit if a signal has occurred after snoop has begun the process
         * of quitting.
         */
        if (quitting)
                exit(1);

        /*
         * If an alarm handler has timed out, and snoop_nrecover has
         * reached SNOOP_MAXRECOVER, skip to the next packet.
         *
         * If any other signal has occurred, and snoop_nrecover has
         * reached SNOOP_MAXRECOVER, give up.
         */
        if (sig == SIGALRM) {
                if (ioctl(STDOUT_FILENO, I_CANPUT, 0) == 0) {
                        /*
                         * We've stalled on output, which is not a critical
                         * failure.  Reset the recovery counter so we do not
                         * consider this a persistent failure, and return so
                         * we do not skip this packet.
                         */
                        snoop_nrecover = 0;
                        return;
                }
                if (snoop_nrecover >= SNOOP_MAXRECOVER) {
                        (void) fprintf(stderr,
                            "snoop: WARNING: skipping from packet %d\n",
                            count);
                        snoop_nrecover = 0;
                } else {
                        /* continue trying */
                        return;
                }
        } else if (snoop_nrecover >= SNOOP_MAXRECOVER) {
                (void) fprintf(stderr,
                    "snoop: ERROR: cannot recover from packet %d\n", count);
                exit(1);
        }

#ifdef DEBUG
        (void) fprintf(stderr, "snoop_sigrecover(%d, %p, %p)\n", sig, info, p);
#endif /* DEBUG */

        /*
         * Prepare to quit. This allows final processing to occur
         * after first terminal interruption.
         */
        if (sig == SIGTERM || sig == SIGHUP || sig == SIGINT) {
                quitting = 1;
                return;
        } else if (sig != -1 && sig != SIGALRM) {
                /* Inform user that snoop has taken a fault */
                (void) fprintf(stderr,
                    "WARNING: received signal %d from packet %d\n",
                    sig, count);
        }

        /* Reset interpreter variables */
        snoop_recover();

        /* Continue in scan() with the next packet */
        siglongjmp(jmp_env, 1);
        /*NOTREACHED*/
}

/*
 * Protected malloc for global error recovery: prepare for interpreter
 * failures with corrupted packets or confused interpreters.  Dynamically
 * allocate `nbytes' bytes, and sandwich it between two PROT_NONE pages to
 * catch writes outside of the allocated region.
 */
static char *
protmalloc(size_t nbytes)
{
        caddr_t start;
        int psz = sysconf(_SC_PAGESIZE);

        nbytes = P2ROUNDUP(nbytes, psz);
        start = mmap(NULL, nbytes + psz * 2, PROT_READ|PROT_WRITE,
            MAP_PRIVATE|MAP_ANON, -1, 0);
        if (start == MAP_FAILED) {
                perror("Error: protmalloc: mmap");
                return (NULL);
        }
        assert(IS_P2ALIGNED(start, psz));
        if (mprotect(start, 1, PROT_NONE) == -1)
                perror("Warning: mprotect");

        start += psz;
        if (mprotect(start + nbytes, 1, PROT_NONE) == -1)
                perror("Warning: mprotect");

        return (start);
}

/*
 * resetperm - reduce security vulnerabilities by resetting
 * owner/group/permissions. Always attempt setuid() - if we have
 * permission to drop our privilege level, do so.
 */
void
resetperm(void)
{
        if (geteuid() == 0) {
                (void) setgid(GID_NOBODY);
                (void) setuid(UID_NOBODY);
        }
}