/* editcap.c * Edit capture files. We can delete packets, adjust timestamps, or * simply convert from one format to another format. * * Originally written by Richard Sharpe. * Improved by Guy Harris. * Further improved by Richard Sharpe. * * Copyright 2013, Richard Sharpe * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #define WS_LOG_DOMAIN LOG_DOMAIN_MAIN #include #include #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include "epan/etypes.h" #include "epan/dissectors/packet-ieee80211-radiotap-defs.h" #ifdef _WIN32 #include /* getpid */ #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui/failure_message.h" #include "ringbuffer.h" /* For RINGBUFFER_MAX_NUM_FILES */ /* Additional exit codes */ #define CANT_EXTRACT_PREFIX 2 #define WRITE_ERROR 2 #define DUMP_ERROR 2 #define NANOSECS_PER_SEC 1000000000 /* * Some globals so we can pass things to various routines */ struct select_item { gboolean inclusive; guint first, second; }; /* * Duplicate frame detection */ typedef struct _fd_hash_t { guint8 digest[16]; guint32 len; nstime_t frame_time; } fd_hash_t; #define DEFAULT_DUP_DEPTH 5 /* Used with -d */ #define MAX_DUP_DEPTH 1000000 /* the maximum window (and actual size of fd_hash[]) for de-duplication */ static fd_hash_t fd_hash[MAX_DUP_DEPTH]; static int dup_window = DEFAULT_DUP_DEPTH; static int cur_dup_entry = 0; static guint32 ignored_bytes = 0; /* Used with -I */ #define ONE_BILLION 1000000000 /* Weights of different errors we can introduce */ /* We should probably make these command-line arguments */ /* XXX - Should we add a bit-level error? */ #define ERR_WT_BIT 5 /* Flip a random bit */ #define ERR_WT_BYTE 5 /* Substitute a random byte */ #define ERR_WT_ALNUM 5 /* Substitute a random character in [A-Za-z0-9] */ #define ERR_WT_FMT 2 /* Substitute "%s" */ #define ERR_WT_AA 1 /* Fill the remainder of the buffer with 0xAA */ #define ERR_WT_TOTAL (ERR_WT_BIT + ERR_WT_BYTE + ERR_WT_ALNUM + ERR_WT_FMT + ERR_WT_AA) #define ALNUM_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" #define ALNUM_LEN (sizeof(ALNUM_CHARS) - 1) struct time_adjustment { nstime_t tv; int is_negative; }; typedef struct _chop_t { int len_begin; int off_begin_pos; int off_begin_neg; int len_end; int off_end_pos; int off_end_neg; } chop_t; /* Table of user comments */ GTree *frames_user_comments = NULL; GPtrArray *capture_comments = NULL; #define MAX_SELECTIONS 512 static struct select_item selectfrm[MAX_SELECTIONS]; static guint max_selected = 0; static gboolean keep_em = FALSE; static int out_file_type_subtype = WTAP_FILE_TYPE_SUBTYPE_UNKNOWN; static int out_frame_type = -2; /* Leave frame type alone */ static gboolean verbose = FALSE; /* Not so verbose */ static struct time_adjustment time_adj = {NSTIME_INIT_ZERO, 0}; /* no adjustment */ static nstime_t relative_time_window = NSTIME_INIT_ZERO; /* de-dup time window */ static double err_prob = -1.0; static nstime_t starttime = NSTIME_INIT_ZERO; static gboolean have_starttime = FALSE; static nstime_t stoptime = NSTIME_INIT_ZERO; static gboolean have_stoptime = FALSE; static gboolean check_startstop = FALSE; static gboolean rem_vlan = FALSE; static gboolean dup_detect = FALSE; static gboolean dup_detect_by_time = FALSE; static gboolean skip_radiotap = FALSE; static gboolean discard_all_secrets = FALSE; static gboolean discard_cap_comments = FALSE; static gboolean set_unused = FALSE; static int do_strict_time_adjustment = FALSE; static struct time_adjustment strict_time_adj = {NSTIME_INIT_ZERO, 0}; /* strict time adjustment */ static nstime_t previous_time = NSTIME_INIT_ZERO; /* previous time */ static const struct { const char *str; guint32 id; } secrets_types[] = { { "tls", SECRETS_TYPE_TLS }, { "ssh", SECRETS_TYPE_SSH }, { "wg", SECRETS_TYPE_WIREGUARD }, }; static int find_dct2000_real_data(guint8 *buf); static void handle_chopping(chop_t chop, wtap_packet_header *out_phdr, const wtap_packet_header *in_phdr, guint8 **buf, gboolean adjlen); static gchar * abs_time_to_str_with_sec_resolution(const nstime_t *abs_time) { struct tm *tmp; gchar *buf = (gchar *)g_malloc(16); tmp = localtime(&abs_time->secs); if (tmp) { snprintf(buf, 16, "%d%02d%02d%02d%02d%02d", tmp->tm_year + 1900, tmp->tm_mon+1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec); } else { buf[0] = '\0'; } return buf; } static gchar * fileset_get_filename_by_pattern(guint idx, const wtap_rec *rec, gchar *fprefix, gchar *fsuffix) { gchar filenum[5+1]; gchar *timestr; gchar *abs_str; snprintf(filenum, sizeof(filenum), "%05u", idx % RINGBUFFER_MAX_NUM_FILES); if (rec->presence_flags & WTAP_HAS_TS) { timestr = abs_time_to_str_with_sec_resolution(&rec->ts); abs_str = g_strconcat(fprefix, "_", filenum, "_", timestr, fsuffix, NULL); g_free(timestr); } else abs_str = g_strconcat(fprefix, "_", filenum, fsuffix, NULL); return abs_str; } static gboolean fileset_extract_prefix_suffix(const char *fname, gchar **fprefix, gchar **fsuffix) { char *pfx, *last_pathsep; gchar *save_file; save_file = g_strdup(fname); if (save_file == NULL) { fprintf(stderr, "editcap: Out of memory\n"); return FALSE; } last_pathsep = strrchr(save_file, G_DIR_SEPARATOR); pfx = strrchr(save_file,'.'); if (pfx != NULL && (last_pathsep == NULL || pfx > last_pathsep)) { /* The pathname has a "." in it, and it's in the last component * of the pathname (because there is either only one component, * i.e. last_pathsep is null as there are no path separators, * or the "." is after the path separator before the last * component. * Treat it as a separator between the rest of the file name and * the file name suffix, and arrange that the names given to the * ring buffer files have the specified suffix, i.e. put the * changing part of the name *before* the suffix. */ pfx[0] = '\0'; *fprefix = g_strdup(save_file); pfx[0] = '.'; /* restore capfile_name */ *fsuffix = g_strdup(pfx); } else { /* Either there's no "." in the pathname, or it's in a directory * component, so the last component has no suffix. */ *fprefix = g_strdup(save_file); *fsuffix = NULL; } g_free(save_file); return TRUE; } /* Add a selection item, a simple parser for now */ static gboolean add_selection(char *sel, guint* max_selection) { char *locn; char *next; if (max_selected >= MAX_SELECTIONS) { /* Let the user know we stopped selecting */ fprintf(stderr, "Out of room for packet selections.\n"); return(FALSE); } if (verbose) fprintf(stderr, "Add_Selected: %s\n", sel); if ((locn = strchr(sel, '-')) == NULL) { /* No dash, so a single number? */ if (verbose) fprintf(stderr, "Not inclusive ..."); selectfrm[max_selected].inclusive = FALSE; selectfrm[max_selected].first = get_guint32(sel, "packet number"); if (selectfrm[max_selected].first > *max_selection) *max_selection = selectfrm[max_selected].first; if (verbose) fprintf(stderr, " %u\n", selectfrm[max_selected].first); } else { if (verbose) fprintf(stderr, "Inclusive ..."); *locn = '\0'; /* split the range */ next = locn + 1; selectfrm[max_selected].inclusive = TRUE; selectfrm[max_selected].first = get_guint32(sel, "beginning of packet range"); selectfrm[max_selected].second = get_guint32(next, "end of packet range"); if (selectfrm[max_selected].second == 0) { /* Not a valid number, presume all */ selectfrm[max_selected].second = *max_selection = G_MAXUINT; } else if (selectfrm[max_selected].second > *max_selection) *max_selection = selectfrm[max_selected].second; if (verbose) fprintf(stderr, " %u, %u\n", selectfrm[max_selected].first, selectfrm[max_selected].second); } max_selected++; return(TRUE); } /* Was the packet selected? */ static gboolean selected(guint recno) { guint i; for (i = 0; i < max_selected; i++) { if (selectfrm[i].inclusive) { if (selectfrm[i].first <= recno && selectfrm[i].second >= recno) return TRUE; } else { if (recno == selectfrm[i].first) return TRUE; } } return FALSE; } static gboolean set_time_adjustment(char *optarg_str_p) { char *frac, *end; long val; size_t frac_digits; if (!optarg_str_p) return TRUE; /* skip leading whitespace */ while (*optarg_str_p == ' ' || *optarg_str_p == '\t') optarg_str_p++; /* check for a negative adjustment */ if (*optarg_str_p == '-') { time_adj.is_negative = 1; optarg_str_p++; } /* collect whole number of seconds, if any */ if (*optarg_str_p == '.') { /* only fractional (i.e., .5 is ok) */ val = 0; frac = optarg_str_p; } else { val = strtol(optarg_str_p, &frac, 10); if (frac == NULL || frac == optarg_str_p || val == LONG_MIN || val == LONG_MAX) { fprintf(stderr, "editcap: \"%s\" isn't a valid time adjustment\n", optarg_str_p); return FALSE; } if (val < 0) { /* implies '--' since we caught '-' above */ fprintf(stderr, "editcap: \"%s\" isn't a valid time adjustment\n", optarg_str_p); return FALSE; } } time_adj.tv.secs = val; /* now collect the partial seconds, if any */ if (*frac != '\0') { /* chars left, so get fractional part */ val = strtol(&(frac[1]), &end, 10); /* if more than 9 fractional digits truncate to 9 */ if ((end - &(frac[1])) > 9) { frac[10] = 't'; /* 't' for truncate */ val = strtol(&(frac[1]), &end, 10); } if (*frac != '.' || end == NULL || end == frac || val < 0 || val >= ONE_BILLION || val == LONG_MIN || val == LONG_MAX) { fprintf(stderr, "editcap: \"%s\" isn't a valid time adjustment\n", optarg_str_p); return FALSE; } } else { return TRUE; /* no fractional digits */ } /* adjust fractional portion from fractional to numerator * e.g., in "1.5" from 5 to 500000000 since .5*10^9 = 500000000 */ frac_digits = end - frac - 1; /* fractional digit count (remember '.') */ while(frac_digits < 9) { /* this is frac of 10^9 */ val *= 10; frac_digits++; } time_adj.tv.nsecs = (int)val; return TRUE; } static gboolean set_strict_time_adj(char *optarg_str_p) { char *frac, *end; long val; size_t frac_digits; if (!optarg_str_p) return TRUE; /* skip leading whitespace */ while (*optarg_str_p == ' ' || *optarg_str_p == '\t') optarg_str_p++; /* * check for a negative adjustment * A negative strict adjustment value is a flag * to adjust all frames by the specifed delta time. */ if (*optarg_str_p == '-') { strict_time_adj.is_negative = 1; optarg_str_p++; } /* collect whole number of seconds, if any */ if (*optarg_str_p == '.') { /* only fractional (i.e., .5 is ok) */ val = 0; frac = optarg_str_p; } else { val = strtol(optarg_str_p, &frac, 10); if (frac == NULL || frac == optarg_str_p || val == LONG_MIN || val == LONG_MAX) { fprintf(stderr, "editcap: \"%s\" isn't a valid time adjustment\n", optarg_str_p); return FALSE; } if (val < 0) { /* implies '--' since we caught '-' above */ fprintf(stderr, "editcap: \"%s\" isn't a valid time adjustment\n", optarg_str_p); return FALSE; } } strict_time_adj.tv.secs = val; /* now collect the partial seconds, if any */ if (*frac != '\0') { /* chars left, so get fractional part */ val = strtol(&(frac[1]), &end, 10); /* if more than 9 fractional digits truncate to 9 */ if ((end - &(frac[1])) > 9) { frac[10] = 't'; /* 't' for truncate */ val = strtol(&(frac[1]), &end, 10); } if (*frac != '.' || end == NULL || end == frac || val < 0 || val >= ONE_BILLION || val == LONG_MIN || val == LONG_MAX) { fprintf(stderr, "editcap: \"%s\" isn't a valid time adjustment\n", optarg_str_p); return FALSE; } } else { return TRUE; /* no fractional digits */ } /* adjust fractional portion from fractional to numerator * e.g., in "1.5" from 5 to 500000000 since .5*10^9 = 500000000 */ frac_digits = end - frac - 1; /* fractional digit count (remember '.') */ while(frac_digits < 9) { /* this is frac of 10^9 */ val *= 10; frac_digits++; } strict_time_adj.tv.nsecs = (int)val; return TRUE; } static gboolean set_rel_time(char *optarg_str_p) { char *frac, *end; long val; size_t frac_digits; if (!optarg_str_p) return TRUE; /* skip leading whitespace */ while (*optarg_str_p == ' ' || *optarg_str_p == '\t') optarg_str_p++; /* ignore negative adjustment */ if (*optarg_str_p == '-') optarg_str_p++; /* collect whole number of seconds, if any */ if (*optarg_str_p == '.') { /* only fractional (i.e., .5 is ok) */ val = 0; frac = optarg_str_p; } else { val = strtol(optarg_str_p, &frac, 10); if (frac == NULL || frac == optarg_str_p || val == LONG_MIN || val == LONG_MAX) { fprintf(stderr, "1: editcap: \"%s\" isn't a valid rel time value\n", optarg_str_p); return FALSE; } if (val < 0) { /* implies '--' since we caught '-' above */ fprintf(stderr, "2: editcap: \"%s\" isn't a valid rel time value\n", optarg_str_p); return FALSE; } } relative_time_window.secs = val; /* now collect the partial seconds, if any */ if (*frac != '\0') { /* chars left, so get fractional part */ val = strtol(&(frac[1]), &end, 10); /* if more than 9 fractional digits truncate to 9 */ if ((end - &(frac[1])) > 9) { frac[10] = 't'; /* 't' for truncate */ val = strtol(&(frac[1]), &end, 10); } if (*frac != '.' || end == NULL || end == frac || val < 0 || val >= ONE_BILLION || val == LONG_MIN || val == LONG_MAX) { fprintf(stderr, "3: editcap: \"%s\" isn't a valid rel time value\n", optarg_str_p); return FALSE; } } else { return TRUE; /* no fractional digits */ } /* adjust fractional portion from fractional to numerator * e.g., in "1.5" from 5 to 500000000 since .5*10^9 = 500000000 */ frac_digits = end - frac - 1; /* fractional digit count (remember '.') */ while(frac_digits < 9) { /* this is frac of 10^9 */ val *= 10; frac_digits++; } relative_time_window.nsecs = (int)val; return TRUE; } #define SLL_ADDRLEN 8 /* length of address field */ struct sll_header { uint16_t sll_pkttype; /* packet type */ uint16_t sll_hatype; /* link-layer address type */ uint16_t sll_halen; /* link-layer address length */ uint8_t sll_addr[SLL_ADDRLEN]; /* link-layer address */ uint16_t sll_protocol; /* protocol */ }; struct sll2_header { uint16_t sll2_protocol; /* protocol */ uint16_t sll2_reserved_mbz; /* reserved - must be zero */ uint32_t sll2_if_index; /* 1-based interface index */ uint16_t sll2_hatype; /* link-layer address type */ uint8_t sll2_pkttype; /* packet type */ uint8_t sll2_halen; /* link-layer address length */ uint8_t sll2_addr[SLL_ADDRLEN]; /* link-layer address */ }; #define VLAN_SIZE 4 static void sll_remove_vlan_info(guint8* fd, guint32* len) { if (pntoh16(fd + offsetof(struct sll_header, sll_protocol)) == ETHERTYPE_VLAN) { int rest_len; /* point to start of vlan */ fd = fd + offsetof(struct sll_header, sll_protocol); /* bytes to read after vlan info */ rest_len = *len - (offsetof(struct sll_header, sll_protocol) + VLAN_SIZE); /* remove vlan info from packet */ memmove(fd, fd + VLAN_SIZE, rest_len); *len -= 4; } } static void sll_set_unused_info(guint8* fd) { guint32 ha_len; ha_len = pntoh16(fd + offsetof(struct sll_header, sll_halen)); if (ha_len < SLL_ADDRLEN) { int unused; unused = SLL_ADDRLEN - ha_len; /* point to end of sll_ddr */ fd = fd + offsetof(struct sll_header, sll_addr) + ha_len; /* set zeros in the unused data */ memset(fd, 0, unused); } } static void sll2_set_unused_info(guint8* fd) { guint32 ha_len; ha_len = *(fd + offsetof(struct sll2_header, sll2_halen)); if (ha_len < SLL_ADDRLEN) { int unused; unused = SLL_ADDRLEN - ha_len; /* point to end of sll2_addr */ fd = fd + offsetof(struct sll2_header, sll2_addr) + ha_len; /* set zeros in the unused data */ memset(fd, 0, unused); } } static void remove_vlan_info(const wtap_packet_header *phdr, guint8* fd, guint32* len) { switch (phdr->pkt_encap) { case WTAP_ENCAP_SLL: sll_remove_vlan_info(fd, len); break; default: /* no support for current pkt_encap */ break; } } static void set_unused_info(const wtap_packet_header *phdr, guint8* fd) { switch (phdr->pkt_encap) { case WTAP_ENCAP_SLL: sll_set_unused_info(fd); break; case WTAP_ENCAP_SLL2: sll2_set_unused_info(fd); break; default: /* no support for current pkt_encap */ break; } } static gboolean is_duplicate(guint8* fd, guint32 len) { int i; const struct ieee80211_radiotap_header* tap_header; /*Hint to ignore some bytes at the start of the frame for the digest calculation(-I option) */ guint32 offset = ignored_bytes; guint32 new_len; guint8 *new_fd; if (len <= ignored_bytes) { offset = 0; } /* Get the size of radiotap header and use that as offset (-p option) */ if (skip_radiotap == TRUE) { tap_header = (const struct ieee80211_radiotap_header*)fd; offset = pletoh16(&tap_header->it_len); if (offset >= len) offset = 0; } new_fd = &fd[offset]; new_len = len - (offset); cur_dup_entry++; if (cur_dup_entry >= dup_window) cur_dup_entry = 0; /* Calculate our digest */ gcry_md_hash_buffer(GCRY_MD_MD5, fd_hash[cur_dup_entry].digest, new_fd, new_len); fd_hash[cur_dup_entry].len = len; /* Look for duplicates */ for (i = 0; i < dup_window; i++) { if (i == cur_dup_entry) continue; if (fd_hash[i].len == fd_hash[cur_dup_entry].len && memcmp(fd_hash[i].digest, fd_hash[cur_dup_entry].digest, 16) == 0) { return TRUE; } } return FALSE; } static gboolean is_duplicate_rel_time(guint8* fd, guint32 len, const nstime_t *current) { int i; /*Hint to ignore some bytes at the start of the frame for the digest calculation(-I option) */ guint32 offset = ignored_bytes; guint32 new_len; guint8 *new_fd; if (len <= ignored_bytes) { offset = 0; } new_fd = &fd[offset]; new_len = len - (offset); cur_dup_entry++; if (cur_dup_entry >= dup_window) cur_dup_entry = 0; /* Calculate our digest */ gcry_md_hash_buffer(GCRY_MD_MD5, fd_hash[cur_dup_entry].digest, new_fd, new_len); fd_hash[cur_dup_entry].len = len; fd_hash[cur_dup_entry].frame_time.secs = current->secs; fd_hash[cur_dup_entry].frame_time.nsecs = current->nsecs; /* * Look for relative time related duplicates. * This is hopefully a reasonably efficient mechanism for * finding duplicates by rel time in the fd_hash[] cache. * We check starting from the most recently added hash * entries and work backwards towards older packets. * This approach allows the dup test to be terminated * when the relative time of a cached entry is found to * be beyond the dup time window. * * Of course this assumes that the input trace file is * "well-formed" in the sense that the packet timestamps are * in strict chronologically increasing order (which is NOT * always the case!!). * * The fd_hash[] table was deliberately created large (1,000,000). * Looking for time related duplicates in large trace files with * non-fractional dup time window values can potentially take * a long time to complete. */ for (i = cur_dup_entry - 1;; i--) { nstime_t delta; int cmp; if (i < 0) i = dup_window - 1; if (i == cur_dup_entry) { /* * We've decremented back to where we started. * Check no more! */ break; } if (nstime_is_unset(&(fd_hash[i].frame_time))) { /* * We've decremented to an unused fd_hash[] entry. * Check no more! */ break; } nstime_delta(&delta, current, &fd_hash[i].frame_time); if (delta.secs < 0 || delta.nsecs < 0) { /* * A negative delta implies that the current packet * has an absolute timestamp less than the cached packet * that it is being compared to. This is NOT a normal * situation since trace files usually have packets in * chronological order (oldest to newest). * * There are several possible ways to deal with this: * 1. 'continue' dup checking with the next cached frame. * 2. 'break' from looking for a duplicate of the current frame. * 3. Take the absolute value of the delta and see if that * falls within the specifed dup time window. * * Currently this code does option 1. But it would pretty * easy to add yet-another-editcap-option to select one of * the other behaviors for dealing with out-of-sequence * packets. */ continue; } cmp = nstime_cmp(&delta, &relative_time_window); if (cmp > 0) { /* * The delta time indicates that we are now looking at * cached packets beyond the specified dup time window. * Check no more! */ break; } else if (fd_hash[i].len == fd_hash[cur_dup_entry].len && memcmp(fd_hash[i].digest, fd_hash[cur_dup_entry].digest, 16) == 0) { return TRUE; } } return FALSE; } static void print_usage(FILE *output) { fprintf(output, "\n"); fprintf(output, "Usage: editcap [options] ... [ [-] ... ]\n"); fprintf(output, "\n"); fprintf(output, " and must both be present; use '-' for stdin or stdout.\n"); fprintf(output, "A single packet or a range of packets can be selected.\n"); fprintf(output, "\n"); fprintf(output, "Packet selection:\n"); fprintf(output, " -r keep the selected packets; default is to delete them.\n"); fprintf(output, " -A only read packets whose timestamp is after (or equal\n"); fprintf(output, " to) the given time.\n"); fprintf(output, " -B only read packets whose timestamp is before the\n"); fprintf(output, " given time.\n"); fprintf(output, " Time format for -A/-B options is\n"); fprintf(output, " YYYY-MM-DDThh:mm:ss[.nnnnnnnnn][Z|+-hh:mm]\n"); fprintf(output, " Unix epoch timestamps are also supported.\n"); fprintf(output, "\n"); fprintf(output, "Duplicate packet removal:\n"); fprintf(output, " --novlan remove vlan info from packets before checking for duplicates.\n"); fprintf(output, " -d remove packet if duplicate (window == %d).\n", DEFAULT_DUP_DEPTH); fprintf(output, " -D remove packet if duplicate; configurable .\n"); fprintf(output, " Valid values are 0 to %d.\n", MAX_DUP_DEPTH); fprintf(output, " NOTE: A of 0 with -V (verbose option) is\n"); fprintf(output, " useful to print MD5 hashes.\n"); fprintf(output, " -w remove packet if duplicate packet is found EQUAL TO OR\n"); fprintf(output, " LESS THAN prior to current packet.\n"); fprintf(output, " A is specified in relative seconds\n"); fprintf(output, " (e.g. 0.000001).\n"); fprintf(output, " NOTE: The use of the 'Duplicate packet removal' options with\n"); fprintf(output, " other editcap options except -V may not always work as expected.\n"); fprintf(output, " Specifically the -r, -t or -S options will very likely NOT have the\n"); fprintf(output, " desired effect if combined with the -d, -D or -w.\n"); fprintf(output, " --skip-radiotap-header skip radiotap header when checking for packet duplicates.\n"); fprintf(output, " Useful when processing packets captured by multiple radios\n"); fprintf(output, " on the same channel in the vicinity of each other.\n"); fprintf(output, " --set-unused set unused byts to zero in sll link addr.\n"); fprintf(output, "\n"); fprintf(output, "Packet manipulation:\n"); fprintf(output, " -s truncate each packet to max. bytes of data.\n"); fprintf(output, " -C [offset:] chop each packet by bytes. Positive values\n"); fprintf(output, " chop at the packet beginning, negative values at the\n"); fprintf(output, " packet end. If an optional offset precedes the length,\n"); fprintf(output, " then the bytes chopped will be offset from that value.\n"); fprintf(output, " Positive offsets are from the packet beginning,\n"); fprintf(output, " negative offsets are from the packet end. You can use\n"); fprintf(output, " this option more than once, allowing up to 2 chopping\n"); fprintf(output, " regions within a packet provided that at least 1\n"); fprintf(output, " choplen is positive and at least 1 is negative.\n"); fprintf(output, " -L adjust the frame (i.e. reported) length when chopping\n"); fprintf(output, " and/or snapping.\n"); fprintf(output, " -t