forked from osmocom/wireshark
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5817 lines
219 KiB
5817 lines
219 KiB
/* dumpcap.c
|
|
*
|
|
* Wireshark - Network traffic analyzer
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
* Copyright 1998 Gerald Combs
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include <config.h>
|
|
#define WS_LOG_DOMAIN LOG_DOMAIN_CAPCHILD
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h> /* for exit() */
|
|
#include <glib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#ifdef HAVE_NETINET_IN_H
|
|
#include <netinet/in.h>
|
|
#endif
|
|
|
|
#include <wsutil/ws_getopt.h>
|
|
|
|
#if defined(__APPLE__) && defined(__LP64__)
|
|
#include <sys/utsname.h>
|
|
#endif
|
|
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
|
|
#include <wsutil/cmdarg_err.h>
|
|
#include <wsutil/strtoi.h>
|
|
#include <cli_main.h>
|
|
#include <wsutil/version_info.h>
|
|
|
|
#include <wsutil/socket.h>
|
|
#include <wsutil/wslog.h>
|
|
#include <wsutil/file_util.h>
|
|
|
|
#ifdef HAVE_LIBCAP
|
|
# include <sys/prctl.h>
|
|
# include <sys/capability.h>
|
|
#endif
|
|
|
|
#include "ringbuffer.h"
|
|
|
|
#include "capture/capture_ifinfo.h"
|
|
#include "capture/capture-pcap-util.h"
|
|
#include "capture/capture-pcap-util-int.h"
|
|
#ifdef _WIN32
|
|
#include "capture/capture-wpcap.h"
|
|
#endif /* _WIN32 */
|
|
|
|
#include "writecap/pcapio.h"
|
|
|
|
#ifndef _WIN32
|
|
#include <sys/un.h>
|
|
#endif
|
|
|
|
#include <wsutil/clopts_common.h>
|
|
#include <wsutil/privileges.h>
|
|
|
|
#include "sync_pipe.h"
|
|
|
|
#include "capture_opts.h"
|
|
#include <capture/capture_session.h>
|
|
#include <capture/capture_sync.h>
|
|
|
|
#include "wsutil/tempfile.h"
|
|
#include "wsutil/file_util.h"
|
|
#include "wsutil/cpu_info.h"
|
|
#include "wsutil/os_version_info.h"
|
|
#include "wsutil/str_util.h"
|
|
#include "wsutil/inet_addr.h"
|
|
#include "wsutil/time_util.h"
|
|
#include "wsutil/please_report_bug.h"
|
|
#include "wsutil/glib-compat.h"
|
|
#include <wsutil/ws_assert.h>
|
|
|
|
#include "capture/ws80211_utils.h"
|
|
|
|
#include "extcap.h"
|
|
|
|
/*
|
|
* Get information about libpcap format from "wiretap/libpcap.h".
|
|
* Get information about pcapng format from "wiretap/pcapng_module.h".
|
|
* XXX - can we just use pcap_open_offline() to read the pipe?
|
|
*/
|
|
#include "wiretap/libpcap.h"
|
|
#include "wiretap/pcapng_module.h"
|
|
#include "wiretap/pcapng.h"
|
|
|
|
/**#define DEBUG_DUMPCAP**/
|
|
/**#define DEBUG_CHILD_DUMPCAP**/
|
|
|
|
#ifdef _WIN32
|
|
#include "wsutil/win32-utils.h"
|
|
#ifdef DEBUG_DUMPCAP
|
|
#include <conio.h> /* _getch() */
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef DEBUG_CHILD_DUMPCAP
|
|
FILE *debug_log; /* for logging debug messages to */
|
|
/* a file if DEBUG_CHILD_DUMPCAP */
|
|
/* is defined */
|
|
#endif
|
|
|
|
static GAsyncQueue *pcap_queue;
|
|
static gint64 pcap_queue_bytes;
|
|
static gint64 pcap_queue_packets;
|
|
static gint64 pcap_queue_byte_limit = 0;
|
|
static gint64 pcap_queue_packet_limit = 0;
|
|
|
|
static gboolean capture_child = FALSE; /* FALSE: standalone call, TRUE: this is an Wireshark capture child */
|
|
static const char *report_capture_filename = NULL; /* capture child file name */
|
|
#ifdef _WIN32
|
|
static gchar *sig_pipe_name = NULL;
|
|
static HANDLE sig_pipe_handle = NULL;
|
|
static gboolean signal_pipe_check_running(void);
|
|
#endif
|
|
|
|
#ifdef SIGINFO
|
|
static gboolean infodelay; /* if TRUE, don't print capture info in SIGINFO handler */
|
|
static gboolean infoprint; /* if TRUE, print capture info after clearing infodelay */
|
|
#endif /* SIGINFO */
|
|
|
|
/** Stop a low-level capture (stops the capture child). */
|
|
static void capture_loop_stop(void);
|
|
/** Close a pipe, or socket if \a from_socket is TRUE */
|
|
static void cap_pipe_close(int pipe_fd, gboolean from_socket);
|
|
|
|
#if defined (__linux__)
|
|
/* whatever the deal with pcap_breakloop, linux doesn't support timeouts
|
|
* in pcap_dispatch(); on the other hand, select() works just fine there.
|
|
* Hence we use a select for that come what may.
|
|
*
|
|
* XXX - with TPACKET_V1 and TPACKET_V2, it currently uses select()
|
|
* internally, and, with TPACKET_V3, once that's supported, it'll
|
|
* support timeouts, at least as I understand the way the code works.
|
|
*/
|
|
#define MUST_DO_SELECT
|
|
#endif
|
|
|
|
/** init the capture filter */
|
|
typedef enum {
|
|
INITFILTER_NO_ERROR,
|
|
INITFILTER_BAD_FILTER,
|
|
INITFILTER_OTHER_ERROR
|
|
} initfilter_status_t;
|
|
|
|
typedef enum {
|
|
STATE_EXPECT_REC_HDR,
|
|
STATE_READ_REC_HDR,
|
|
STATE_EXPECT_DATA,
|
|
STATE_READ_DATA
|
|
} cap_pipe_state_t;
|
|
|
|
typedef enum {
|
|
PIPOK,
|
|
PIPEOF,
|
|
PIPERR,
|
|
PIPNEXIST
|
|
} cap_pipe_err_t;
|
|
|
|
typedef struct _pcap_pipe_info {
|
|
gboolean byte_swapped; /**< TRUE if data in the pipe is byte swapped. */
|
|
struct pcap_hdr hdr; /**< Pcap header when capturing from a pipe */
|
|
struct pcaprec_modified_hdr rechdr; /**< Pcap record header when capturing from a pipe */
|
|
} pcap_pipe_info_t;
|
|
|
|
typedef struct _pcapng_pipe_info {
|
|
pcapng_block_header_t bh; /**< Pcapng general block header when capturing from a pipe */
|
|
GArray *src_iface_to_global; /**< Int array mapping local IDB numbers to global_ld.interface_data */
|
|
} pcapng_pipe_info_t;
|
|
|
|
struct _loop_data; /* forward declaration so we can use it in the cap_pipe_dispatch function pointer */
|
|
|
|
/*
|
|
* A source of packets from which we're capturing.
|
|
*/
|
|
typedef struct _capture_src {
|
|
guint32 received;
|
|
guint32 dropped;
|
|
guint32 flushed;
|
|
pcap_t *pcap_h;
|
|
#ifdef MUST_DO_SELECT
|
|
int pcap_fd; /**< pcap file descriptor */
|
|
#endif
|
|
gboolean pcap_err;
|
|
guint interface_id;
|
|
GThread *tid;
|
|
int snaplen;
|
|
int linktype;
|
|
gboolean ts_nsec; /**< TRUE if we're using nanosecond precision. */
|
|
/**< capture pipe (unix only "input file") */
|
|
gboolean from_cap_pipe; /**< TRUE if we are capturing data from a capture pipe */
|
|
gboolean from_cap_socket; /**< TRUE if we're capturing from socket */
|
|
gboolean from_pcapng; /**< TRUE if we're capturing from pcapng format */
|
|
union {
|
|
pcap_pipe_info_t pcap; /**< Pcap info when capturing from a pipe */
|
|
pcapng_pipe_info_t pcapng; /**< Pcapng info when capturing from a pipe */
|
|
} cap_pipe_info;
|
|
#ifdef _WIN32
|
|
HANDLE cap_pipe_h; /**< The handle of the capture pipe */
|
|
#endif
|
|
int cap_pipe_fd; /**< the file descriptor of the capture pipe */
|
|
gboolean cap_pipe_modified; /**< TRUE if data in the pipe uses modified pcap headers */
|
|
char * cap_pipe_databuf; /**< Pointer to the data buffer we've allocated */
|
|
size_t cap_pipe_databuf_size; /**< Current size of the data buffer */
|
|
guint cap_pipe_max_pkt_size; /**< Maximum packet size allowed */
|
|
#if defined(_WIN32)
|
|
char * cap_pipe_buf; /**< Pointer to the buffer we read into */
|
|
DWORD cap_pipe_bytes_to_read; /**< Used by cap_pipe_dispatch */
|
|
DWORD cap_pipe_bytes_read; /**< Used by cap_pipe_dispatch */
|
|
#else
|
|
size_t cap_pipe_bytes_to_read; /**< Used by cap_pipe_dispatch */
|
|
size_t cap_pipe_bytes_read; /**< Used by cap_pipe_dispatch */
|
|
#endif
|
|
int (*cap_pipe_dispatch)(struct _loop_data *, struct _capture_src *, char *, size_t);
|
|
cap_pipe_state_t cap_pipe_state;
|
|
cap_pipe_err_t cap_pipe_err;
|
|
|
|
#if defined(_WIN32)
|
|
GMutex *cap_pipe_read_mtx;
|
|
GAsyncQueue *cap_pipe_pending_q, *cap_pipe_done_q;
|
|
#endif
|
|
} capture_src;
|
|
|
|
typedef struct _saved_idb {
|
|
gboolean deleted;
|
|
guint interface_id; /* capture_src->interface_id for the associated SHB */
|
|
guint8 *idb; /* If non-NULL, IDB read from capture_src. This is an interface specified on the command line otherwise. */
|
|
guint idb_len;
|
|
} saved_idb_t;
|
|
|
|
/*
|
|
* Global capture loop state.
|
|
*/
|
|
typedef struct _loop_data {
|
|
/* common */
|
|
gboolean go; /**< TRUE as long as we're supposed to keep capturing */
|
|
int err; /**< if non-zero, error seen while capturing */
|
|
gint packets_captured; /**< Number of packets we have already captured */
|
|
guint inpkts_to_sync_pipe; /**< Packets not already send out to the sync_pipe */
|
|
#ifdef SIGINFO
|
|
gboolean report_packet_count; /**< Set by SIGINFO handler; print packet count */
|
|
#endif
|
|
GArray *pcaps; /**< Array of capture_src's on which we're capturing */
|
|
gboolean pcapng_passthrough; /**< We have one source and it's pcapng. Pass its SHB and IDBs through. */
|
|
guint8 *saved_shb; /**< SHB to write when we have one pcapng input */
|
|
GArray *saved_idbs; /**< Array of saved_idb_t, written when we have a new section or output file. */
|
|
GRWLock saved_shb_idb_lock; /**< Saved IDB RW mutex */
|
|
/* output file(s) */
|
|
FILE *pdh;
|
|
int save_file_fd;
|
|
char *io_buffer; /**< Our IO buffer if we increase the size from the standard size */
|
|
guint64 bytes_written; /**< Bytes written for the current file. */
|
|
/* autostop conditions */
|
|
int packets_written; /**< Packets written for the current file. */
|
|
int file_count;
|
|
/* ring buffer conditions */
|
|
GTimer *file_duration_timer;
|
|
time_t next_interval_time;
|
|
int interval_s;
|
|
} loop_data;
|
|
|
|
typedef struct _pcap_queue_element {
|
|
capture_src *pcap_src;
|
|
union {
|
|
struct pcap_pkthdr phdr;
|
|
pcapng_block_header_t bh;
|
|
} u;
|
|
u_char *pd;
|
|
} pcap_queue_element;
|
|
|
|
/*
|
|
* This needs to be static, so that the SIGINT handler can clear the "go"
|
|
* flag and for saved_shb_idb_lock.
|
|
*/
|
|
static loop_data global_ld;
|
|
|
|
/*
|
|
* Timeout, in milliseconds, for reads from the stream of captured packets
|
|
* from a capture device.
|
|
*
|
|
* A bug in Mac OS X 10.6 and 10.6.1 causes calls to pcap_open_live(), in
|
|
* 64-bit applications, with sub-second timeouts not to work. The bug is
|
|
* fixed in 10.6.2, re-broken in 10.6.3, and again fixed in 10.6.5.
|
|
*/
|
|
#if defined(__APPLE__) && defined(__LP64__)
|
|
static gboolean need_timeout_workaround;
|
|
|
|
#define CAP_READ_TIMEOUT (need_timeout_workaround ? 1000 : 250)
|
|
#else
|
|
#define CAP_READ_TIMEOUT 250
|
|
#endif
|
|
|
|
/*
|
|
* Timeout, in microseconds, for reads from the stream of captured packets
|
|
* from a pipe. Pipes don't have the same problem that BPF devices do
|
|
* in Mac OS X 10.6, 10.6.1, 10.6.3, and 10.6.4, so we always use a timeout
|
|
* of 250ms, i.e. the same value as CAP_READ_TIMEOUT when not on one
|
|
* of the offending versions of Snow Leopard.
|
|
*
|
|
* On Windows this value is converted to milliseconds and passed to
|
|
* WaitForSingleObject. If it's less than 1000 WaitForSingleObject
|
|
* will return immediately.
|
|
*/
|
|
#if defined(_WIN32)
|
|
#define PIPE_READ_TIMEOUT 100000
|
|
#else
|
|
#define PIPE_READ_TIMEOUT 250000
|
|
#endif
|
|
|
|
#define WRITER_THREAD_TIMEOUT 100000 /* usecs */
|
|
|
|
static void
|
|
dumpcap_log_writer(const char *domain, enum ws_log_level level,
|
|
struct timespec timestamp,
|
|
const char *file, long line, const char *func,
|
|
const char *user_format, va_list user_ap,
|
|
void *user_data);
|
|
|
|
/* capture related options */
|
|
static capture_options global_capture_opts;
|
|
static GPtrArray *capture_comments = NULL;
|
|
static gboolean quiet = FALSE;
|
|
static gboolean use_threads = FALSE;
|
|
static guint64 start_time;
|
|
|
|
static void capture_loop_write_packet_cb(u_char *pcap_src_p, const struct pcap_pkthdr *phdr,
|
|
const u_char *pd);
|
|
static void capture_loop_queue_packet_cb(u_char *pcap_src_p, const struct pcap_pkthdr *phdr,
|
|
const u_char *pd);
|
|
static void capture_loop_write_pcapng_cb(capture_src *pcap_src, const pcapng_block_header_t *bh, u_char *pd);
|
|
static void capture_loop_queue_pcapng_cb(capture_src *pcap_src, const pcapng_block_header_t *bh, u_char *pd);
|
|
static void capture_loop_get_errmsg(char *errmsg, size_t errmsglen,
|
|
char *secondary_errmsg,
|
|
size_t secondary_errmsglen,
|
|
const char *fname, int err,
|
|
gboolean is_close);
|
|
|
|
WS_NORETURN static void exit_main(int err);
|
|
|
|
static void report_new_capture_file(const char *filename);
|
|
static void report_packet_count(unsigned int packet_count);
|
|
static void report_packet_drops(guint32 received, guint32 pcap_drops, guint32 drops, guint32 flushed, guint32 ps_ifdrop, gchar *name);
|
|
static void report_capture_error(const char *error_msg, const char *secondary_error_msg);
|
|
static void report_cfilter_error(capture_options *capture_opts, guint i, const char *errmsg);
|
|
|
|
#define MSG_MAX_LENGTH 4096
|
|
|
|
static void
|
|
print_usage(FILE *output)
|
|
{
|
|
fprintf(output, "\nUsage: dumpcap [options] ...\n");
|
|
fprintf(output, "\n");
|
|
fprintf(output, "Capture interface:\n");
|
|
fprintf(output, " -i <interface>, --interface <interface>\n");
|
|
fprintf(output, " name or idx of interface (def: first non-loopback),\n"
|
|
" or for remote capturing, use one of these formats:\n"
|
|
" rpcap://<host>/<interface>\n"
|
|
" TCP@<host>:<port>\n");
|
|
fprintf(output, " --ifname <name> name to use in the capture file for a pipe from which\n");
|
|
fprintf(output, " we're capturing\n");
|
|
fprintf(output, " --ifdescr <description>\n");
|
|
fprintf(output, " description to use in the capture file for a pipe\n");
|
|
fprintf(output, " from which we're capturing\n");
|
|
fprintf(output, " -f <capture filter> packet filter in libpcap filter syntax\n");
|
|
fprintf(output, " -s <snaplen>, --snapshot-length <snaplen>\n");
|
|
#ifdef HAVE_PCAP_CREATE
|
|
fprintf(output, " packet snapshot length (def: appropriate maximum)\n");
|
|
#else
|
|
fprintf(output, " packet snapshot length (def: %u)\n", WTAP_MAX_PACKET_SIZE_STANDARD);
|
|
#endif
|
|
fprintf(output, " -p, --no-promiscuous-mode\n");
|
|
fprintf(output, " don't capture in promiscuous mode\n");
|
|
#ifdef HAVE_PCAP_CREATE
|
|
fprintf(output, " -I, --monitor-mode capture in monitor mode, if available\n");
|
|
#endif
|
|
#ifdef CAN_SET_CAPTURE_BUFFER_SIZE
|
|
fprintf(output, " -B <buffer size>, --buffer-size <buffer size>\n");
|
|
fprintf(output, " size of kernel buffer in MiB (def: %dMiB)\n", DEFAULT_CAPTURE_BUFFER_SIZE);
|
|
#endif
|
|
fprintf(output, " -y <link type>, --linktype <link type>\n");
|
|
fprintf(output, " link layer type (def: first appropriate)\n");
|
|
fprintf(output, " --time-stamp-type <type> timestamp method for interface\n");
|
|
fprintf(output, " -D, --list-interfaces print list of interfaces and exit\n");
|
|
fprintf(output, " -L, --list-data-link-types\n");
|
|
fprintf(output, " print list of link-layer types of iface and exit\n");
|
|
fprintf(output, " --list-time-stamp-types print list of timestamp types for iface and exit\n");
|
|
fprintf(output, " -d print generated BPF code for capture filter\n");
|
|
fprintf(output, " -k <freq>,[<type>],[<center_freq1>],[<center_freq2>]\n");
|
|
fprintf(output, " set channel on wifi interface\n");
|
|
fprintf(output, " -S print statistics for each interface once per second\n");
|
|
fprintf(output, " -M for -D, -L, and -S, produce machine-readable output\n");
|
|
fprintf(output, "\n");
|
|
#ifdef HAVE_PCAP_REMOTE
|
|
fprintf(output, "RPCAP options:\n");
|
|
fprintf(output, " -r don't ignore own RPCAP traffic in capture\n");
|
|
fprintf(output, " -u use UDP for RPCAP data transfer\n");
|
|
fprintf(output, " -A <user>:<password> use RPCAP password authentication\n");
|
|
#ifdef HAVE_PCAP_SETSAMPLING
|
|
fprintf(output, " -m <sampling type> use packet sampling\n");
|
|
fprintf(output, " count:NUM - capture one packet of every NUM\n");
|
|
fprintf(output, " timer:NUM - capture no more than 1 packet in NUM ms\n");
|
|
#endif
|
|
#endif
|
|
fprintf(output, "Stop conditions:\n");
|
|
fprintf(output, " -c <packet count> stop after n packets (def: infinite)\n");
|
|
fprintf(output, " -a <autostop cond.> ..., --autostop <autostop cond.> ...\n");
|
|
fprintf(output, " duration:NUM - stop after NUM seconds\n");
|
|
fprintf(output, " filesize:NUM - stop this file after NUM kB\n");
|
|
fprintf(output, " files:NUM - stop after NUM files\n");
|
|
fprintf(output, " packets:NUM - stop after NUM packets\n");
|
|
/*fprintf(output, "\n");*/
|
|
fprintf(output, "Output (files):\n");
|
|
fprintf(output, " -w <filename> name of file to save (def: tempfile)\n");
|
|
fprintf(output, " -g enable group read access on the output file(s)\n");
|
|
fprintf(output, " -b <ringbuffer opt.> ..., --ring-buffer <ringbuffer opt.>\n");
|
|
fprintf(output, " duration:NUM - switch to next file after NUM secs\n");
|
|
fprintf(output, " filesize:NUM - switch to next file after NUM kB\n");
|
|
fprintf(output, " files:NUM - ringbuffer: replace after NUM files\n");
|
|
fprintf(output, " packets:NUM - ringbuffer: replace after NUM packets\n");
|
|
fprintf(output, " interval:NUM - switch to next file when the time is\n");
|
|
fprintf(output, " an exact multiple of NUM secs\n");
|
|
fprintf(output, " printname:FILE - print filename to FILE when written\n");
|
|
fprintf(output, " (can use 'stdout' or 'stderr')\n");
|
|
fprintf(output, " -n use pcapng format instead of pcap (default)\n");
|
|
fprintf(output, " -P use libpcap format instead of pcapng\n");
|
|
fprintf(output, " --capture-comment <comment>\n");
|
|
fprintf(output, " add a capture comment to the output file\n");
|
|
fprintf(output, " (only for pcapng)\n");
|
|
fprintf(output, " --temp-dir <directory> write temporary files to this directory\n");
|
|
fprintf(output, " (default: %s)\n", g_get_tmp_dir());
|
|
fprintf(output, "\n");
|
|
|
|
ws_log_print_usage(output);
|
|
fprintf(output, "\n");
|
|
|
|
fprintf(output, "Miscellaneous:\n");
|
|
fprintf(output, " -N <packet_limit> maximum number of packets buffered within dumpcap\n");
|
|
fprintf(output, " -C <byte_limit> maximum number of bytes used for buffering packets\n");
|
|
fprintf(output, " within dumpcap\n");
|
|
fprintf(output, " -t use a separate thread per interface\n");
|
|
fprintf(output, " -q don't report packet capture counts\n");
|
|
fprintf(output, " -v, --version print version information and exit\n");
|
|
fprintf(output, " -h, --help display this help and exit\n");
|
|
fprintf(output, "\n");
|
|
#ifdef __linux__
|
|
fprintf(output, "Dumpcap can benefit from an enabled BPF JIT compiler if available.\n");
|
|
fprintf(output, "You might want to enable it by executing:\n");
|
|
fprintf(output, " \"echo 1 > /proc/sys/net/core/bpf_jit_enable\"\n");
|
|
fprintf(output, "Note that this can make your system less secure!\n");
|
|
fprintf(output, "\n");
|
|
#endif
|
|
fprintf(output, "Example: dumpcap -i eth0 -a duration:60 -w output.pcapng\n");
|
|
fprintf(output, "\"Capture packets from interface eth0 until 60s passed into output.pcapng\"\n");
|
|
fprintf(output, "\n");
|
|
fprintf(output, "Use Ctrl-C to stop capturing at any time.\n");
|
|
}
|
|
|
|
/*
|
|
* Report an error in command-line arguments.
|
|
* If we're a capture child, send a message back to the parent, otherwise
|
|
* just print it.
|
|
*/
|
|
static void
|
|
dumpcap_cmdarg_err(const char *fmt, va_list ap)
|
|
{
|
|
if (capture_child) {
|
|
gchar *msg;
|
|
/* Generate a 'special format' message back to parent */
|
|
msg = ws_strdup_vprintf(fmt, ap);
|
|
sync_pipe_errmsg_to_parent(2, msg, "");
|
|
g_free(msg);
|
|
} else {
|
|
fprintf(stderr, "dumpcap: ");
|
|
vfprintf(stderr, fmt, ap);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Report additional information for an error in command-line arguments.
|
|
* If we're a capture child, send a message back to the parent, otherwise
|
|
* just print it.
|
|
*/
|
|
static void
|
|
dumpcap_cmdarg_err_cont(const char *fmt, va_list ap)
|
|
{
|
|
if (capture_child) {
|
|
gchar *msg;
|
|
msg = ws_strdup_vprintf(fmt, ap);
|
|
sync_pipe_errmsg_to_parent(2, msg, "");
|
|
g_free(msg);
|
|
} else {
|
|
vfprintf(stderr, fmt, ap);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_LIBCAP
|
|
static void
|
|
#if 0 /* Set to enable capability debugging */
|
|
/* see 'man cap_to_text()' for explanation of output */
|
|
/* '=' means 'all= ' ie: no capabilities */
|
|
/* '=ip' means 'all=ip' ie: all capabilities are permissible and inheritable */
|
|
/* .... */
|
|
print_caps(const char *pfx) {
|
|
cap_t caps = cap_get_proc();
|
|
ws_debug("%s: EUID: %d Capabilities: %s", pfx, geteuid(), cap_to_text(caps, NULL));
|
|
cap_free(caps);
|
|
}
|
|
#else
|
|
print_caps(const char *pfx _U_) {
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
relinquish_all_capabilities(void)
|
|
{
|
|
/* Drop any and all capabilities this process may have. */
|
|
/* Allowed whether or not process has any privileges. */
|
|
cap_t caps = cap_init(); /* all capabilities initialized to off */
|
|
print_caps("Pre-clear");
|
|
if (cap_set_proc(caps)) {
|
|
cmdarg_err("cap_set_proc() fail return: %s", g_strerror(errno));
|
|
}
|
|
print_caps("Post-clear");
|
|
cap_free(caps);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Platform-dependent suggestions for fixing permissions.
|
|
*/
|
|
#if defined(__linux__)
|
|
#define PLATFORM_PERMISSIONS_SUGGESTION \
|
|
"\n\n" \
|
|
"On Debian and Debian derivatives such as Ubuntu, if you have " \
|
|
"installed Wireshark from a package, try running" \
|
|
"\n\n" \
|
|
" sudo dpkg-reconfigure wireshark-common" \
|
|
"\n\n" \
|
|
"selecting \"<Yes>\" in response to the question" \
|
|
"\n\n" \
|
|
" Should non-superusers be able to capture packets?" \
|
|
"\n\n" \
|
|
"adding yourself to the \"wireshark\" group by running" \
|
|
"\n\n" \
|
|
" sudo usermod -a -G wireshark {your username}" \
|
|
"\n\n" \
|
|
"and then logging out and logging back in again."
|
|
#elif defined(__APPLE__)
|
|
#define PLATFORM_PERMISSIONS_SUGGESTION \
|
|
"\n\n" \
|
|
"If you installed Wireshark using the package from wireshark.org, " \
|
|
"close this dialog and click on the \"installing ChmodBPF\" link in " \
|
|
"\"You can fix this by installing ChmodBPF.\" on the main screen, " \
|
|
"and then complete the installation procedure."
|
|
#else
|
|
#define PLATFORM_PERMISSIONS_SUGGESTION
|
|
#endif
|
|
|
|
static const char *
|
|
get_pcap_failure_secondary_error_message(cap_device_open_status open_status,
|
|
const char *open_status_str)
|
|
{
|
|
#ifdef _WIN32
|
|
/*
|
|
* On Windows, first make sure they *have* Npcap installed.
|
|
*/
|
|
if (!has_wpcap) {
|
|
return
|
|
"In order to capture packets, Npcap or WinPcap must be installed. See\n"
|
|
"\n"
|
|
" https://npcap.com/\n"
|
|
"\n"
|
|
"for a downloadable version of Npcap and for instructions on how to\n"
|
|
"install it.";
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* OK, now just return a largely platform-independent error that might
|
|
* have platform-specific suggestions at the end (for example, suggestions
|
|
* for how to get permission to capture).
|
|
*/
|
|
if (open_status == CAP_DEVICE_OPEN_ERR_GENERIC) {
|
|
/*
|
|
* We don't know what kind of error it is. See if there's a hint
|
|
* in the error string; if not, throw all generic suggestions at
|
|
* the user.
|
|
*/
|
|
static const char promisc_failed[] =
|
|
"failed to set hardware filter to promiscuous mode";
|
|
|
|
/*
|
|
* Does the error string begin with the error produced by WinPcap
|
|
* and Npcap if attempting to set promiscuous mode fails?
|
|
* (Note that this string could have a specific error message
|
|
* from an NDIS error after the initial part, so we do a prefix
|
|
* check rather than an exact match check.)
|
|
*/
|
|
if (strncmp(open_status_str, promisc_failed, sizeof promisc_failed - 1) == 0) {
|
|
/*
|
|
* Yes. Suggest that the user turn off promiscuous mode on that
|
|
* device.
|
|
*/
|
|
return
|
|
"Please turn off promiscuous mode for this device";
|
|
} else {
|
|
return
|
|
"Please check to make sure you have sufficient permissions, and that you have "
|
|
"the proper interface or pipe specified."
|
|
PLATFORM_PERMISSIONS_SUGGESTION;
|
|
}
|
|
} else if (open_status == CAP_DEVICE_OPEN_ERR_PERMISSIONS) {
|
|
/*
|
|
* This is a permissions error, so no need to specify any other
|
|
* warnings.
|
|
*/
|
|
return
|
|
"Please check to make sure you have sufficient permissions."
|
|
PLATFORM_PERMISSIONS_SUGGESTION;
|
|
} else {
|
|
/*
|
|
* This is not a permissons error, so no need to suggest
|
|
* checking permissions.
|
|
*/
|
|
return
|
|
"Please check that you have the proper interface or pipe specified.";
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_capture_device_open_failure_messages(cap_device_open_status open_status,
|
|
const char *open_status_str,
|
|
const char *iface,
|
|
char *errmsg, size_t errmsg_len,
|
|
char *secondary_errmsg,
|
|
size_t secondary_errmsg_len)
|
|
{
|
|
snprintf(errmsg, (gulong) errmsg_len,
|
|
"The capture session could not be initiated on capture device \"%s\" (%s).",
|
|
iface, open_status_str);
|
|
snprintf(secondary_errmsg, (gulong) secondary_errmsg_len, "%s",
|
|
get_pcap_failure_secondary_error_message(open_status, open_status_str));
|
|
}
|
|
|
|
static gboolean
|
|
compile_capture_filter(const char *iface, pcap_t *pcap_h,
|
|
struct bpf_program *fcode, const char *cfilter)
|
|
{
|
|
bpf_u_int32 netnum, netmask;
|
|
gchar lookup_net_err_str[PCAP_ERRBUF_SIZE];
|
|
|
|
if (pcap_lookupnet(iface, &netnum, &netmask, lookup_net_err_str) < 0) {
|
|
/*
|
|
* Well, we can't get the netmask for this interface; it's used
|
|
* only for filters that check for broadcast IP addresses, so
|
|
* we just punt and use 0. It might be nice to warn the user,
|
|
* but that's a pain in a GUI application, as it'd involve popping
|
|
* up a message box, and it's not clear how often this would make
|
|
* a difference (only filters that check for IP broadcast addresses
|
|
* use the netmask).
|
|
*/
|
|
/*cmdarg_err(
|
|
"Warning: Couldn't obtain netmask info (%s).", lookup_net_err_str);*/
|
|
netmask = 0;
|
|
}
|
|
|
|
/*
|
|
* Sigh. Older versions of libpcap don't properly declare the
|
|
* third argument to pcap_compile() as a const pointer. Cast
|
|
* away the warning.
|
|
*/
|
|
DIAG_OFF(cast-qual)
|
|
if (pcap_compile(pcap_h, fcode, (char *)cfilter, 1, netmask) < 0)
|
|
return FALSE;
|
|
DIAG_ON(cast-qual)
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
show_filter_code(capture_options *capture_opts)
|
|
{
|
|
interface_options *interface_opts;
|
|
pcap_t *pcap_h;
|
|
cap_device_open_status open_status;
|
|
gchar open_status_str[PCAP_ERRBUF_SIZE];
|
|
char errmsg[MSG_MAX_LENGTH+1];
|
|
char secondary_errmsg[MSG_MAX_LENGTH+1];
|
|
struct bpf_program fcode;
|
|
struct bpf_insn *insn;
|
|
u_int i;
|
|
guint j;
|
|
|
|
for (j = 0; j < capture_opts->ifaces->len; j++) {
|
|
interface_opts = &g_array_index(capture_opts->ifaces, interface_options, j);
|
|
pcap_h = open_capture_device(capture_opts, interface_opts,
|
|
CAP_READ_TIMEOUT, &open_status, &open_status_str);
|
|
if (pcap_h == NULL) {
|
|
/* Open failed; get messages */
|
|
get_capture_device_open_failure_messages(open_status, open_status_str,
|
|
interface_opts->name,
|
|
errmsg, sizeof errmsg,
|
|
secondary_errmsg,
|
|
sizeof secondary_errmsg);
|
|
/* And report them */
|
|
report_capture_error(errmsg, secondary_errmsg);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Set the link-layer type. */
|
|
if (!set_pcap_datalink(pcap_h, interface_opts->linktype, interface_opts->name,
|
|
errmsg, sizeof errmsg,
|
|
secondary_errmsg, sizeof secondary_errmsg)) {
|
|
pcap_close(pcap_h);
|
|
report_capture_error(errmsg, secondary_errmsg);
|
|
return FALSE;
|
|
}
|
|
|
|
/* OK, try to compile the capture filter. */
|
|
if (!compile_capture_filter(interface_opts->name, pcap_h, &fcode,
|
|
interface_opts->cfilter)) {
|
|
snprintf(errmsg, sizeof(errmsg), "%s", pcap_geterr(pcap_h));
|
|
pcap_close(pcap_h);
|
|
report_cfilter_error(capture_opts, j, errmsg);
|
|
return FALSE;
|
|
}
|
|
pcap_close(pcap_h);
|
|
|
|
/* Now print the filter code. */
|
|
insn = fcode.bf_insns;
|
|
|
|
for (i = 0; i < fcode.bf_len; insn++, i++)
|
|
printf("%s\n", bpf_image(insn, i));
|
|
}
|
|
/* If not using libcap: we now can now set euid/egid to ruid/rgid */
|
|
/* to remove any suid privileges. */
|
|
/* If using libcap: we can now remove NET_RAW and NET_ADMIN capabilities */
|
|
/* (euid/egid have already previously been set to ruid/rgid. */
|
|
/* (See comment in main() for details) */
|
|
#ifndef HAVE_LIBCAP
|
|
relinquish_special_privs_perm();
|
|
#else
|
|
relinquish_all_capabilities();
|
|
#endif
|
|
if (capture_child) {
|
|
/* Let our parent know we succeeded. */
|
|
pipe_write_block(2, SP_SUCCESS, NULL);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* capture_interface_list() is expected to do the right thing to get
|
|
* a list of interfaces.
|
|
*
|
|
* In most of the programs in the Wireshark suite, "the right thing"
|
|
* is to run dumpcap and ask it for the list, because dumpcap may
|
|
* be the only program in the suite with enough privileges to get
|
|
* the list.
|
|
*
|
|
* In dumpcap itself, however, we obviously can't run dumpcap to
|
|
* ask for the list. Therefore, our capture_interface_list() should
|
|
* just call get_interface_list().
|
|
*/
|
|
GList *
|
|
capture_interface_list(int *err, char **err_str, void(*update_cb)(void) _U_)
|
|
{
|
|
return get_interface_list(err, err_str);
|
|
}
|
|
|
|
/*
|
|
* Output a machine readable list of the interfaces
|
|
* This list is retrieved by the sync_interface_list_open() function
|
|
* The actual output of this function can be viewed with the command "dumpcap -D -Z none"
|
|
*/
|
|
static void
|
|
print_machine_readable_interfaces(GList *if_list)
|
|
{
|
|
int i;
|
|
GList *if_entry;
|
|
if_info_t *if_info;
|
|
GSList *addr;
|
|
if_addr_t *if_addr;
|
|
char addr_str[WS_INET6_ADDRSTRLEN];
|
|
|
|
if (capture_child) {
|
|
/* Let our parent know we succeeded. */
|
|
pipe_write_block(2, SP_SUCCESS, NULL);
|
|
}
|
|
|
|
i = 1; /* Interface id number */
|
|
for (if_entry = g_list_first(if_list); if_entry != NULL;
|
|
if_entry = g_list_next(if_entry)) {
|
|
if_info = (if_info_t *)if_entry->data;
|
|
printf("%d. %s\t", i++, if_info->name);
|
|
|
|
/*
|
|
* Print the contents of the if_entry struct in a parseable format.
|
|
* Each if_entry element is tab-separated. Addresses are comma-
|
|
* separated.
|
|
*/
|
|
/* XXX - Make sure our description doesn't contain a tab */
|
|
if (if_info->vendor_description != NULL)
|
|
printf("%s\t", if_info->vendor_description);
|
|
else
|
|
printf("\t");
|
|
|
|
/* XXX - Make sure our friendly name doesn't contain a tab */
|
|
if (if_info->friendly_name != NULL)
|
|
printf("%s\t", if_info->friendly_name);
|
|
else
|
|
printf("\t");
|
|
|
|
printf("%i\t", if_info->type);
|
|
|
|
for (addr = g_slist_nth(if_info->addrs, 0); addr != NULL;
|
|
addr = g_slist_next(addr)) {
|
|
if (addr != g_slist_nth(if_info->addrs, 0))
|
|
printf(",");
|
|
|
|
if_addr = (if_addr_t *)addr->data;
|
|
switch(if_addr->ifat_type) {
|
|
case IF_AT_IPv4:
|
|
printf("%s", ws_inet_ntop4(&if_addr->addr.ip4_addr, addr_str, sizeof(addr_str)));
|
|
break;
|
|
case IF_AT_IPv6:
|
|
printf("%s", ws_inet_ntop6(&if_addr->addr.ip6_addr, addr_str, sizeof(addr_str)));
|
|
break;
|
|
default:
|
|
printf("<type unknown %i>", if_addr->ifat_type);
|
|
}
|
|
}
|
|
|
|
if (if_info->loopback)
|
|
printf("\tloopback");
|
|
else
|
|
printf("\tnetwork");
|
|
printf("\t%s", if_info->extcap);
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If you change the machine-readable output format of this function,
|
|
* you MUST update capture_ifinfo.c:capture_get_if_capabilities() accordingly!
|
|
*/
|
|
static void
|
|
print_machine_readable_if_capabilities(if_capabilities_t *caps, int queries)
|
|
{
|
|
GList *lt_entry, *ts_entry;
|
|
const gchar *desc_str;
|
|
|
|
if (capture_child) {
|
|
/* Let our parent know we succeeded. */
|
|
pipe_write_block(2, SP_SUCCESS, NULL);
|
|
}
|
|
|
|
if (queries & CAPS_QUERY_LINK_TYPES) {
|
|
if (caps->can_set_rfmon)
|
|
printf("1\n");
|
|
else
|
|
printf("0\n");
|
|
for (lt_entry = caps->data_link_types; lt_entry != NULL;
|
|
lt_entry = g_list_next(lt_entry)) {
|
|
data_link_info_t *data_link_info = (data_link_info_t *)lt_entry->data;
|
|
if (data_link_info->description != NULL)
|
|
desc_str = data_link_info->description;
|
|
else
|
|
desc_str = "(not supported)";
|
|
printf("%d\t%s\t%s\n", data_link_info->dlt, data_link_info->name,
|
|
desc_str);
|
|
}
|
|
}
|
|
printf("\n");
|
|
if (queries & CAPS_QUERY_TIMESTAMP_TYPES) {
|
|
for (ts_entry = caps->timestamp_types; ts_entry != NULL;
|
|
ts_entry = g_list_next(ts_entry)) {
|
|
timestamp_info_t *timestamp = (timestamp_info_t *)ts_entry->data;
|
|
if (timestamp->description != NULL)
|
|
desc_str = timestamp->description;
|
|
else
|
|
desc_str = "(none)";
|
|
printf("%s\t%s\n", timestamp->name, desc_str);
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
char *name;
|
|
pcap_t *pch;
|
|
} if_stat_t;
|
|
|
|
/* Print the number of packets captured for each interface until we're killed. */
|
|
static int
|
|
print_statistics_loop(gboolean machine_readable)
|
|
{
|
|
GList *if_list, *if_entry, *stat_list = NULL, *stat_entry;
|
|
if_info_t *if_info;
|
|
if_stat_t *if_stat;
|
|
int err;
|
|
gchar *err_str;
|
|
pcap_t *pch;
|
|
char errbuf[PCAP_ERRBUF_SIZE];
|
|
struct pcap_stat ps;
|
|
|
|
if_list = get_interface_list(&err, &err_str);
|
|
if (if_list == NULL) {
|
|
if (err == 0)
|
|
cmdarg_err("There are no interfaces on which a capture can be done");
|
|
else {
|
|
cmdarg_err("%s", err_str);
|
|
g_free(err_str);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
for (if_entry = g_list_first(if_list); if_entry != NULL; if_entry = g_list_next(if_entry)) {
|
|
if_info = (if_info_t *)if_entry->data;
|
|
|
|
#ifdef __linux__
|
|
/* On Linux nf* interfaces don't collect stats properly and don't allows multiple
|
|
* connections. We avoid collecting stats on them.
|
|
*/
|
|
if (!strncmp(if_info->name, "nf", 2)) {
|
|
ws_debug("Skipping interface %s for stats", if_info->name);
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_PCAP_OPEN
|
|
pch = pcap_open(if_info->name, MIN_PACKET_SIZE, 0, 0, NULL, errbuf);
|
|
#else
|
|
pch = pcap_open_live(if_info->name, MIN_PACKET_SIZE, 0, 0, errbuf);
|
|
#endif
|
|
|
|
if (pch) {
|
|
if_stat = g_new(if_stat_t, 1);
|
|
if_stat->name = g_strdup(if_info->name);
|
|
if_stat->pch = pch;
|
|
stat_list = g_list_append(stat_list, if_stat);
|
|
}
|
|
}
|
|
|
|
if (!stat_list) {
|
|
cmdarg_err("There are no interfaces on which a capture can be done");
|
|
return 2;
|
|
}
|
|
|
|
if (capture_child) {
|
|
/* Let our parent know we succeeded. */
|
|
pipe_write_block(2, SP_SUCCESS, NULL);
|
|
}
|
|
|
|
if (!machine_readable) {
|
|
printf("%-15s %10s %10s\n", "Interface", "Received",
|
|
"Dropped");
|
|
}
|
|
|
|
global_ld.go = TRUE;
|
|
while (global_ld.go) {
|
|
for (stat_entry = g_list_first(stat_list); stat_entry != NULL; stat_entry = g_list_next(stat_entry)) {
|
|
if_stat = (if_stat_t *)stat_entry->data;
|
|
pcap_stats(if_stat->pch, &ps);
|
|
|
|
if (!machine_readable) {
|
|
printf("%-15s %10u %10u\n", if_stat->name,
|
|
ps.ps_recv, ps.ps_drop);
|
|
} else {
|
|
printf("%s\t%u\t%u\n", if_stat->name,
|
|
ps.ps_recv, ps.ps_drop);
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
#ifdef _WIN32
|
|
/* If we have a dummy signal pipe check it */
|
|
if (!signal_pipe_check_running()) {
|
|
global_ld.go = FALSE;
|
|
}
|
|
Sleep(1 * 1000);
|
|
#else
|
|
sleep(1);
|
|
#endif
|
|
}
|
|
|
|
/* XXX - Not reached. Should we look for 'q' in stdin? */
|
|
for (stat_entry = g_list_first(stat_list); stat_entry != NULL; stat_entry = g_list_next(stat_entry)) {
|
|
if_stat = (if_stat_t *)stat_entry->data;
|
|
pcap_close(if_stat->pch);
|
|
g_free(if_stat->name);
|
|
g_free(if_stat);
|
|
}
|
|
g_list_free(stat_list);
|
|
free_interface_list(if_list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef _WIN32
|
|
static BOOL WINAPI
|
|
capture_cleanup_handler(DWORD dwCtrlType)
|
|
{
|
|
/* CTRL_C_EVENT is sort of like SIGINT, CTRL_BREAK_EVENT is unique to
|
|
Windows, CTRL_CLOSE_EVENT is sort of like SIGHUP, CTRL_LOGOFF_EVENT
|
|
is also sort of like SIGHUP, and CTRL_SHUTDOWN_EVENT is sort of
|
|
like SIGTERM at least when the machine's shutting down.
|
|
|
|
For now, if we're running as a command rather than a capture child,
|
|
we handle all but CTRL_LOGOFF_EVENT as indications that we should
|
|
clean up and quit, just as we handle SIGINT, SIGHUP, and SIGTERM
|
|
in that way on UN*X.
|
|
|
|
If we're not running as a capture child, we might be running as
|
|
a service; ignore CTRL_LOGOFF_EVENT, so we keep running after the
|
|
user logs out. (XXX - can we explicitly check whether we're
|
|
running as a service?) */
|
|
|
|
ws_info("Console: Control signal");
|
|
ws_debug("Console: Control signal, CtrlType: %lu", dwCtrlType);
|
|
|
|
/* Keep capture running if we're a service and a user logs off */
|
|
if (capture_child || (dwCtrlType != CTRL_LOGOFF_EVENT)) {
|
|
capture_loop_stop();
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
#else
|
|
static void
|
|
capture_cleanup_handler(int signum _U_)
|
|
{
|
|
/* On UN*X, we cleanly shut down the capture on SIGINT, SIGHUP, and
|
|
SIGTERM. We assume that if the user wanted it to keep running
|
|
after they logged out, they'd have nohupped it. */
|
|
|
|
capture_loop_stop();
|
|
}
|
|
#endif
|
|
|
|
|
|
static void
|
|
report_capture_count(gboolean reportit)
|
|
{
|
|
/* Don't print this if we're a capture child. */
|
|
if (!capture_child && reportit) {
|
|
fprintf(stderr, "\rPackets captured: %d\n", global_ld.packets_captured);
|
|
/* stderr could be line buffered */
|
|
fflush(stderr);
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef SIGINFO
|
|
static void
|
|
report_counts_for_siginfo(void)
|
|
{
|
|
report_capture_count(quiet);
|
|
infoprint = FALSE; /* we just reported it */
|
|
}
|
|
|
|
static void
|
|
report_counts_siginfo(int signum _U_)
|
|
{
|
|
int sav_errno = errno;
|
|
|
|
/* If we've been told to delay printing, just set a flag asking
|
|
that we print counts (if we're supposed to), otherwise print
|
|
the count of packets captured (if we're supposed to). */
|
|
if (infodelay)
|
|
infoprint = TRUE;
|
|
else
|
|
report_counts_for_siginfo();
|
|
errno = sav_errno;
|
|
}
|
|
#endif /* SIGINFO */
|
|
|
|
static void
|
|
exit_main(int status)
|
|
{
|
|
ws_cleanup_sockets();
|
|
|
|
#ifdef _WIN32
|
|
/* can be helpful for debugging */
|
|
#ifdef DEBUG_DUMPCAP
|
|
printf("Press any key\n");
|
|
_getch();
|
|
#endif
|
|
|
|
#endif /* _WIN32 */
|
|
|
|
if (ringbuf_is_initialized()) {
|
|
/* save_file is managed by ringbuffer, be sure to release the memory and
|
|
* avoid capture_opts_cleanup from double-freeing 'save_file'. */
|
|
ringbuf_free();
|
|
global_capture_opts.save_file = NULL;
|
|
}
|
|
|
|
capture_opts_cleanup(&global_capture_opts);
|
|
exit(status);
|
|
}
|
|
|
|
#ifdef HAVE_LIBCAP
|
|
/*
|
|
* If we were linked with libcap (not related to libpcap), make sure we have
|
|
* CAP_NET_ADMIN and CAP_NET_RAW, then relinquish our permissions.
|
|
* (See comment in main() for details)
|
|
*/
|
|
static void
|
|
relinquish_privs_except_capture(void)
|
|
{
|
|
/* If 'started_with_special_privs' (ie: suid) then enable for
|
|
* ourself the NET_ADMIN and NET_RAW capabilities and then
|
|
* drop our suid privileges.
|
|
*
|
|
* CAP_NET_ADMIN: Promiscuous mode and a truckload of other
|
|
* stuff we don't need (and shouldn't have).
|
|
* CAP_NET_RAW: Packet capture (raw sockets).
|
|
*/
|
|
|
|
if (started_with_special_privs()) {
|
|
cap_value_t cap_list[2] = { CAP_NET_ADMIN, CAP_NET_RAW };
|
|
int cl_len = sizeof(cap_list) / sizeof(cap_value_t);
|
|
|
|
cap_t caps = cap_init(); /* all capabilities initialized to off */
|
|
|
|
print_caps("Pre drop, pre set");
|
|
|
|
if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
|
|
cmdarg_err("prctl() fail return: %s", g_strerror(errno));
|
|
}
|
|
|
|
cap_set_flag(caps, CAP_PERMITTED, cl_len, cap_list, CAP_SET);
|
|
cap_set_flag(caps, CAP_INHERITABLE, cl_len, cap_list, CAP_SET);
|
|
|
|
if (cap_set_proc(caps)) {
|
|
cmdarg_err("cap_set_proc() fail return: %s", g_strerror(errno));
|
|
}
|
|
print_caps("Pre drop, post set");
|
|
|
|
relinquish_special_privs_perm();
|
|
|
|
print_caps("Post drop, pre set");
|
|
cap_set_flag(caps, CAP_EFFECTIVE, cl_len, cap_list, CAP_SET);
|
|
if (cap_set_proc(caps)) {
|
|
cmdarg_err("cap_set_proc() fail return: %s", g_strerror(errno));
|
|
}
|
|
print_caps("Post drop, post set");
|
|
|
|
cap_free(caps);
|
|
}
|
|
}
|
|
|
|
#endif /* HAVE_LIBCAP */
|
|
|
|
/* Take care of byte order in the libpcap headers read from pipes.
|
|
* (function taken from wiretap/libpcap.c) */
|
|
static void
|
|
cap_pipe_adjust_pcap_header(gboolean byte_swapped, struct pcap_hdr *hdr, struct pcaprec_hdr *rechdr)
|
|
{
|
|
if (byte_swapped) {
|
|
/* Byte-swap the record header fields. */
|
|
rechdr->ts_sec = GUINT32_SWAP_LE_BE(rechdr->ts_sec);
|
|
rechdr->ts_usec = GUINT32_SWAP_LE_BE(rechdr->ts_usec);
|
|
rechdr->incl_len = GUINT32_SWAP_LE_BE(rechdr->incl_len);
|
|
rechdr->orig_len = GUINT32_SWAP_LE_BE(rechdr->orig_len);
|
|
}
|
|
|
|
/* In file format version 2.3, the "incl_len" and "orig_len" fields were
|
|
swapped, in order to match the BPF header layout.
|
|
|
|
Unfortunately, some files were, according to a comment in the "libpcap"
|
|
source, written with version 2.3 in their headers but without the
|
|
interchanged fields, so if "incl_len" is greater than "orig_len" - which
|
|
would make no sense - we assume that we need to swap them. */
|
|
if (hdr->version_major == 2 &&
|
|
(hdr->version_minor < 3 ||
|
|
(hdr->version_minor == 3 && rechdr->incl_len > rechdr->orig_len))) {
|
|
guint32 temp;
|
|
|
|
temp = rechdr->orig_len;
|
|
rechdr->orig_len = rechdr->incl_len;
|
|
rechdr->incl_len = temp;
|
|
}
|
|
}
|
|
|
|
/* Wrapper: distinguish between recv/read if we're reading on Windows,
|
|
* or just read().
|
|
*/
|
|
static ssize_t
|
|
cap_pipe_read(int pipe_fd, char *buf, size_t sz, gboolean from_socket _U_)
|
|
{
|
|
#ifdef _WIN32
|
|
if (from_socket) {
|
|
return recv(pipe_fd, buf, (int)sz, 0);
|
|
} else {
|
|
return -1;
|
|
}
|
|
#else
|
|
return ws_read(pipe_fd, buf, sz);
|
|
#endif
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
/*
|
|
* Thread function that reads from a pipe and pushes the data
|
|
* to the main application thread.
|
|
*/
|
|
/*
|
|
* XXX Right now we use async queues for basic signaling. The main thread
|
|
* sets cap_pipe_buf and cap_bytes_to_read, then pushes an item onto
|
|
* cap_pipe_pending_q which triggers a read in the cap_pipe_read thread.
|
|
* Iff the read is successful cap_pipe_read pushes an item onto
|
|
* cap_pipe_done_q, otherwise an error is signaled. No data is passed in
|
|
* the queues themselves (yet).
|
|
*
|
|
* We might want to move some of the cap_pipe_dispatch logic here so that
|
|
* we can let cap_thread_read run independently, queuing up multiple reads
|
|
* for the main thread (and possibly get rid of cap_pipe_read_mtx).
|
|
*/
|
|
static void *cap_thread_read(void *arg)
|
|
{
|
|
capture_src *pcap_src;
|
|
#ifdef _WIN32
|
|
BOOL res;
|
|
DWORD last_err, bytes_read;
|
|
#else /* _WIN32 */
|
|
size_t bytes_read;
|
|
#endif /* _WIN32 */
|
|
|
|
pcap_src = (capture_src *)arg;
|
|
while (pcap_src->cap_pipe_err == PIPOK) {
|
|
g_async_queue_pop(pcap_src->cap_pipe_pending_q); /* Wait for our cue (ahem) from the main thread */
|
|
g_mutex_lock(pcap_src->cap_pipe_read_mtx);
|
|
bytes_read = 0;
|
|
while (bytes_read < pcap_src->cap_pipe_bytes_to_read) {
|
|
if ((pcap_src->from_cap_socket)
|
|
#ifndef _WIN32
|
|
|| 1
|
|
#endif
|
|
)
|
|
{
|
|
ssize_t b;
|
|
b = cap_pipe_read(pcap_src->cap_pipe_fd, pcap_src->cap_pipe_buf+bytes_read,
|
|
pcap_src->cap_pipe_bytes_to_read - bytes_read, pcap_src->from_cap_socket);
|
|
if (b <= 0) {
|
|
if (b == 0) {
|
|
pcap_src->cap_pipe_err = PIPEOF;
|
|
bytes_read = 0;
|
|
break;
|
|
} else {
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
bytes_read = -1;
|
|
break;
|
|
}
|
|
} else {
|
|
bytes_read += (DWORD)b;
|
|
}
|
|
}
|
|
#ifdef _WIN32
|
|
else
|
|
{
|
|
/* If we try to use read() on a named pipe on Windows with partial
|
|
* data it appears to return EOF.
|
|
*/
|
|
DWORD b;
|
|
res = ReadFile(pcap_src->cap_pipe_h, pcap_src->cap_pipe_buf+bytes_read,
|
|
pcap_src->cap_pipe_bytes_to_read - bytes_read,
|
|
&b, NULL);
|
|
|
|
bytes_read += b;
|
|
if (!res) {
|
|
last_err = GetLastError();
|
|
if (last_err == ERROR_MORE_DATA) {
|
|
continue;
|
|
} else if (last_err == ERROR_HANDLE_EOF || last_err == ERROR_BROKEN_PIPE || last_err == ERROR_PIPE_NOT_CONNECTED) {
|
|
pcap_src->cap_pipe_err = PIPEOF;
|
|
bytes_read = 0;
|
|
break;
|
|
}
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
bytes_read = -1;
|
|
break;
|
|
} else if (b == 0 && pcap_src->cap_pipe_bytes_to_read > 0) {
|
|
pcap_src->cap_pipe_err = PIPEOF;
|
|
bytes_read = 0;
|
|
break;
|
|
}
|
|
}
|
|
#endif /*_WIN32 */
|
|
}
|
|
pcap_src->cap_pipe_bytes_read = bytes_read;
|
|
if (pcap_src->cap_pipe_bytes_read >= pcap_src->cap_pipe_bytes_to_read) {
|
|
g_async_queue_push(pcap_src->cap_pipe_done_q, pcap_src->cap_pipe_buf); /* Any non-NULL value will do */
|
|
}
|
|
g_mutex_unlock(pcap_src->cap_pipe_read_mtx);
|
|
}
|
|
/* Post to queue if we didn't read enough data as the main thread waits for the message */
|
|
g_mutex_lock(pcap_src->cap_pipe_read_mtx);
|
|
if (pcap_src->cap_pipe_bytes_read < pcap_src->cap_pipe_bytes_to_read) {
|
|
/* There's still more of the record to read. */
|
|
g_async_queue_push(pcap_src->cap_pipe_done_q, pcap_src->cap_pipe_buf); /* Any non-NULL value will do */
|
|
}
|
|
g_mutex_unlock(pcap_src->cap_pipe_read_mtx);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Do a blocking read from a pipe within the main thread, by pushing
|
|
* the read onto the pipe queue and then popping it off that queue;
|
|
* the pipe will block until the pushed read completes.
|
|
*
|
|
* We do it with another thread because we can't use select() on
|
|
* pipes on Windows, as we can on UN*Xes, we can only use it on
|
|
* sockets.
|
|
*/
|
|
void
|
|
pipe_read_sync(capture_src *pcap_src, void *buf, DWORD nbytes)
|
|
{
|
|
pcap_src->cap_pipe_buf = (char *) buf;
|
|
pcap_src->cap_pipe_bytes_read = 0;
|
|
pcap_src->cap_pipe_bytes_to_read = nbytes;
|
|
/* We don't have to worry about cap_pipe_read_mtx here */
|
|
g_async_queue_push(pcap_src->cap_pipe_pending_q, pcap_src->cap_pipe_buf);
|
|
g_async_queue_pop(pcap_src->cap_pipe_done_q);
|
|
}
|
|
#endif
|
|
|
|
/* Provide select() functionality for a single file descriptor
|
|
* on UNIX/POSIX. Windows uses cap_pipe_read via a thread.
|
|
*
|
|
* Returns the same values as select.
|
|
*/
|
|
static int
|
|
cap_pipe_select(int pipe_fd)
|
|
{
|
|
fd_set rfds;
|
|
struct timeval timeout;
|
|
|
|
FD_ZERO(&rfds);
|
|
FD_SET(pipe_fd, &rfds);
|
|
|
|
timeout.tv_sec = PIPE_READ_TIMEOUT / 1000000;
|
|
timeout.tv_usec = PIPE_READ_TIMEOUT % 1000000;
|
|
|
|
return select(pipe_fd+1, &rfds, NULL, NULL, &timeout);
|
|
}
|
|
|
|
#define DEF_TCP_PORT 19000
|
|
|
|
static int
|
|
cap_open_socket(char *pipename, capture_src *pcap_src, char *errmsg, size_t errmsgl)
|
|
{
|
|
struct sockaddr_storage sa;
|
|
socklen_t sa_len;
|
|
int fd;
|
|
|
|
/* Skip the initial "TCP@" in the pipename. */
|
|
if (ws_socket_ptoa(&sa, pipename + 4, DEF_TCP_PORT) < 0) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"The capture session could not be initiated because"
|
|
"\"%s\" is not a valid socket specification", pipename);
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
return -1;
|
|
}
|
|
|
|
if ((fd = (int)socket(sa.ss_family, SOCK_STREAM, 0)) < 0) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"The capture session could not be initiated because"
|
|
" the socket couldn't be created due to the socket error: \n"
|
|
#ifdef _WIN32
|
|
" %s", win32strerror(WSAGetLastError()));
|
|
#else
|
|
" %d: %s", errno, g_strerror(errno));
|
|
#endif
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
return -1;
|
|
}
|
|
|
|
if (sa.ss_family == AF_INET6)
|
|
sa_len = sizeof(struct sockaddr_in6);
|
|
else
|
|
sa_len = sizeof(struct sockaddr_in);
|
|
if (connect(fd, (struct sockaddr *)&sa, sa_len) < 0) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"The capture session could not be initiated because"
|
|
" the socket couldn't be connected due to the socket error: \n"
|
|
#ifdef _WIN32
|
|
" %s", win32strerror(WSAGetLastError()));
|
|
#else
|
|
" %d: %s", errno, g_strerror(errno));
|
|
#endif
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
|
|
cap_pipe_close(fd, TRUE);
|
|
return -1;
|
|
}
|
|
|
|
pcap_src->from_cap_socket = TRUE;
|
|
return fd;
|
|
}
|
|
|
|
/* Wrapper: distinguish between closesocket on Windows; use ws_close
|
|
* otherwise.
|
|
*/
|
|
static void
|
|
cap_pipe_close(int pipe_fd, gboolean from_socket)
|
|
{
|
|
#ifdef _WIN32
|
|
if (from_socket) {
|
|
closesocket(pipe_fd);
|
|
}
|
|
#else
|
|
(void) from_socket; /* Mark unused, similar to Q_UNUSED */
|
|
ws_close(pipe_fd);
|
|
#endif
|
|
}
|
|
|
|
/** Read bytes from a capture source, which is assumed to be a pipe or
|
|
* socket.
|
|
*
|
|
* Returns -1, or the number of bytes read similar to read(2).
|
|
* Sets pcap_src->cap_pipe_err on error or EOF.
|
|
*/
|
|
static ssize_t
|
|
cap_pipe_read_data_bytes(capture_src *pcap_src, char *errmsg, size_t errmsgl)
|
|
{
|
|
int sel_ret;
|
|
int fd = pcap_src->cap_pipe_fd;
|
|
#ifdef _WIN32
|
|
DWORD sz, bytes_read = 0;
|
|
#else /* _WIN32 */
|
|
ssize_t sz, bytes_read = 0;
|
|
#endif /* _WIN32 */
|
|
ssize_t b;
|
|
|
|
#ifdef LOG_CAPTURE_VERBOSE
|
|
ws_debug("cap_pipe_read_data_bytes read %lu of %lu",
|
|
pcap_src->cap_pipe_bytes_read, pcap_src->cap_pipe_bytes_to_read);
|
|
#endif
|
|
sz = pcap_src->cap_pipe_bytes_to_read - pcap_src->cap_pipe_bytes_read;
|
|
while (bytes_read < sz) {
|
|
if (fd == -1) {
|
|
snprintf(errmsg, (gulong)errmsgl, "Invalid file descriptor.");
|
|
pcap_src->cap_pipe_err = PIPNEXIST;
|
|
return -1;
|
|
}
|
|
|
|
sel_ret = cap_pipe_select(fd);
|
|
if (sel_ret < 0) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Unexpected error from select: %s.", g_strerror(errno));
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
return -1;
|
|
} else if (sel_ret > 0) {
|
|
b = cap_pipe_read(fd, pcap_src->cap_pipe_databuf+pcap_src->cap_pipe_bytes_read+bytes_read,
|
|
sz-bytes_read, pcap_src->from_cap_socket);
|
|
if (b <= 0) {
|
|
if (b == 0) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"End of file reading from pipe or socket.");
|
|
pcap_src->cap_pipe_err = PIPEOF;
|
|
} else {
|
|
#ifdef _WIN32
|
|
/*
|
|
* On Windows, we only do this for sockets.
|
|
*/
|
|
DWORD lastError = WSAGetLastError();
|
|
errno = lastError;
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Error reading from pipe or socket: %s.",
|
|
win32strerror(lastError));
|
|
#else
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Error reading from pipe or socket: %s.",
|
|
g_strerror(errno));
|
|
#endif
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
}
|
|
return -1;
|
|
}
|
|
#ifdef _WIN32
|
|
bytes_read += (DWORD)b;
|
|
#else
|
|
bytes_read += b;
|
|
#endif
|
|
}
|
|
}
|
|
pcap_src->cap_pipe_bytes_read += bytes_read;
|
|
#ifdef LOG_CAPTURE_VERBOSE
|
|
ws_debug("cap_pipe_read_data_bytes read %lu of %lu",
|
|
pcap_src->cap_pipe_bytes_read, pcap_src->cap_pipe_bytes_to_read);
|
|
#endif
|
|
return bytes_read;
|
|
}
|
|
|
|
/* Some forward declarations for breaking up cap_pipe_open_live for pcap and pcapng formats */
|
|
static void pcap_pipe_open_live(int fd, capture_src *pcap_src,
|
|
struct pcap_hdr *hdr,
|
|
char *errmsg, size_t errmsgl,
|
|
char *secondary_errmsg, size_t secondary_errmsgl);
|
|
static void pcapng_pipe_open_live(int fd, capture_src *pcap_src,
|
|
char *errmsg, size_t errmsgl);
|
|
static int pcapng_pipe_dispatch(loop_data *ld, capture_src *pcap_src,
|
|
char *errmsg, size_t errmsgl);
|
|
|
|
/* For problems that are probably Not Our Fault. */
|
|
static char not_our_bug[] =
|
|
"Please report this to the developers of the program writing to the pipe.";
|
|
|
|
/* Mimic pcap_open_live() for pipe captures
|
|
|
|
* We check if "pipename" is "-" (stdin), a AF_UNIX socket, or a FIFO,
|
|
* open it, and read the header.
|
|
*
|
|
* N.B. : we can't read the libpcap formats used in RedHat 6.1 or SuSE 6.3
|
|
* because we can't seek on pipes (see wiretap/libpcap.c for details) */
|
|
static void
|
|
cap_pipe_open_live(char *pipename,
|
|
capture_src *pcap_src,
|
|
void *hdr,
|
|
char *errmsg, size_t errmsgl,
|
|
char *secondary_errmsg, size_t secondary_errmsgl)
|
|
{
|
|
#ifndef _WIN32
|
|
ws_statb64 pipe_stat;
|
|
struct sockaddr_un sa;
|
|
#else /* _WIN32 */
|
|
char *pncopy, *pos;
|
|
guintptr extcap_pipe_handle;
|
|
#endif
|
|
gboolean extcap_pipe = FALSE;
|
|
ssize_t b;
|
|
int fd = -1, sel_ret;
|
|
size_t bytes_read;
|
|
guint32 magic = 0;
|
|
pcap_src->cap_pipe_fd = -1;
|
|
#ifdef _WIN32
|
|
pcap_src->cap_pipe_h = INVALID_HANDLE_VALUE;
|
|
#endif
|
|
|
|
ws_debug("cap_pipe_open_live: %s", pipename);
|
|
|
|
/*
|
|
* XXX - this blocks until a pcap per-file header has been written to
|
|
* the pipe, so it could block indefinitely.
|
|
*/
|
|
if (strcmp(pipename, "-") == 0) {
|
|
#ifndef _WIN32
|
|
fd = 0; /* read from stdin */
|
|
#else /* _WIN32 */
|
|
pcap_src->cap_pipe_h = GetStdHandle(STD_INPUT_HANDLE);
|
|
#endif /* _WIN32 */
|
|
} else if (!strncmp(pipename, "TCP@", 4)) {
|
|
if ((fd = cap_open_socket(pipename, pcap_src, errmsg, errmsgl)) < 0) {
|
|
return;
|
|
}
|
|
} else {
|
|
#ifndef _WIN32
|
|
if ( g_strrstr(pipename, EXTCAP_PIPE_PREFIX) != NULL )
|
|
extcap_pipe = TRUE;
|
|
|
|
if (ws_stat64(pipename, &pipe_stat) < 0) {
|
|
if (errno == ENOENT || errno == ENOTDIR)
|
|
pcap_src->cap_pipe_err = PIPNEXIST;
|
|
else {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"The capture session could not be initiated "
|
|
"due to error getting information on pipe or socket: %s.", g_strerror(errno));
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
}
|
|
return;
|
|
}
|
|
if (S_ISFIFO(pipe_stat.st_mode)) {
|
|
fd = ws_open(pipename, O_RDONLY | O_NONBLOCK, 0000 /* no creation so don't matter */);
|
|
if (fd == -1) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"The capture session could not be initiated "
|
|
"due to error on pipe open: %s.", g_strerror(errno));
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
return;
|
|
}
|
|
} else if (S_ISSOCK(pipe_stat.st_mode)) {
|
|
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (fd == -1) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"The capture session could not be initiated "
|
|
"due to error on socket create: %s.", g_strerror(errno));
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
return;
|
|
}
|
|
sa.sun_family = AF_UNIX;
|
|
/*
|
|
* The Single UNIX Specification says:
|
|
*
|
|
* The size of sun_path has intentionally been left undefined.
|
|
* This is because different implementations use different sizes.
|
|
* For example, 4.3 BSD uses a size of 108, and 4.4 BSD uses a size
|
|
* of 104. Since most implementations originate from BSD versions,
|
|
* the size is typically in the range 92 to 108.
|
|
*
|
|
* Applications should not assume a particular length for sun_path
|
|
* or assume that it can hold {_POSIX_PATH_MAX} bytes (256).
|
|
*
|
|
* It also says
|
|
*
|
|
* The <sys/un.h> header shall define the sockaddr_un structure,
|
|
* which shall include at least the following members:
|
|
*
|
|
* sa_family_t sun_family Address family.
|
|
* char sun_path[] Socket pathname.
|
|
*
|
|
* so we assume that it's an array, with a specified size,
|
|
* and that the size reflects the maximum path length.
|
|
*/
|
|
if (g_strlcpy(sa.sun_path, pipename, sizeof sa.sun_path) > sizeof sa.sun_path) {
|
|
/* Path name too long */
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"The capture session coud not be initiated "
|
|
"due to error on socket connect: Path name too long.");
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
ws_close(fd);
|
|
return;
|
|
}
|
|
b = connect(fd, (struct sockaddr *)&sa, sizeof sa);
|
|
if (b == -1) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"The capture session coud not be initiated "
|
|
"due to error on socket connect: %s.", g_strerror(errno));
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
ws_close(fd);
|
|
return;
|
|
}
|
|
} else {
|
|
if (S_ISCHR(pipe_stat.st_mode)) {
|
|
/*
|
|
* Assume the user specified an interface on a system where
|
|
* interfaces are in /dev. Pretend we haven't seen it.
|
|
*/
|
|
pcap_src->cap_pipe_err = PIPNEXIST;
|
|
} else {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"The capture session could not be initiated because\n"
|
|
"\"%s\" is neither an interface nor a socket nor a pipe.", pipename);
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
}
|
|
return;
|
|
}
|
|
|
|
#else /* _WIN32 */
|
|
if (sscanf(pipename, EXTCAP_PIPE_PREFIX "%" SCNuPTR, &extcap_pipe_handle) == 1)
|
|
{
|
|
/* The client is already connected to extcap pipe.
|
|
* We have inherited the handle from parent process.
|
|
*/
|
|
extcap_pipe = TRUE;
|
|
pcap_src->cap_pipe_h = (HANDLE)extcap_pipe_handle;
|
|
}
|
|
else
|
|
{
|
|
#define PIPE_STR "\\pipe\\"
|
|
/* Under Windows, named pipes _must_ have the form
|
|
* "\\<server>\pipe\<pipename>". <server> may be "." for localhost.
|
|
*/
|
|
pncopy = g_strdup(pipename);
|
|
if ((pos = strstr(pncopy, "\\\\")) == pncopy) {
|
|
pos = strchr(pncopy + 3, '\\');
|
|
if (pos && g_ascii_strncasecmp(pos, PIPE_STR, strlen(PIPE_STR)) != 0)
|
|
pos = NULL;
|
|
}
|
|
|
|
g_free(pncopy);
|
|
|
|
if (!pos) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"The capture session could not be initiated because\n"
|
|
"\"%s\" is neither an interface nor a pipe.", pipename);
|
|
pcap_src->cap_pipe_err = PIPNEXIST;
|
|
return;
|
|
}
|
|
|
|
|
|
/* Wait for the pipe to appear */
|
|
while (1) {
|
|
pcap_src->cap_pipe_h = CreateFile(utf_8to16(pipename), GENERIC_READ, 0, NULL,
|
|
OPEN_EXISTING, 0, NULL);
|
|
|
|
if (pcap_src->cap_pipe_h != INVALID_HANDLE_VALUE)
|
|
break;
|
|
|
|
if (GetLastError() != ERROR_PIPE_BUSY) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"The capture session on \"%s\" could not be started "
|
|
"due to error on pipe open: %s.",
|
|
pipename, win32strerror(GetLastError()));
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
return;
|
|
}
|
|
|
|
if (!WaitNamedPipe(utf_8to16(pipename), 30 * 1000)) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"The capture session on \"%s\" timed out during "
|
|
"pipe open: %s.",
|
|
pipename, win32strerror(GetLastError()));
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif /* _WIN32 */
|
|
}
|
|
|
|
pcap_src->from_cap_pipe = TRUE;
|
|
|
|
/*
|
|
* We start with a 2KB buffer for packet data, which should be
|
|
* large enough for most regular network packets. We increase it,
|
|
* up to the maximum size we allow, as necessary.
|
|
*/
|
|
pcap_src->cap_pipe_databuf = (char*)g_malloc(2048);
|
|
pcap_src->cap_pipe_databuf_size = 2048;
|
|
|
|
/*
|
|
* Read the first 4 bytes of data from the pipe.
|
|
*
|
|
* If a pcap file is being written to it, that will be
|
|
* the pcap magic number.
|
|
*
|
|
* If a pcapng file is being written to it, that will be
|
|
* the block type of the initial SHB.
|
|
*/
|
|
#ifdef _WIN32
|
|
/*
|
|
* On UN*X, we can use select() on pipes or sockets.
|
|
*
|
|
* On Windows, we can only use it on sockets; to do non-blocking
|
|
* reads from pipes, we currently do reads in a separate thread
|
|
* and use GLib asynchronous queues from the main thread to start
|
|
* read operations and to wait for them to complete.
|
|
*/
|
|
if (pcap_src->from_cap_socket)
|
|
#endif
|
|
{
|
|
bytes_read = 0;
|
|
while (bytes_read < sizeof magic) {
|
|
sel_ret = cap_pipe_select(fd);
|
|
if (sel_ret < 0) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Unexpected error from select: %s.",
|
|
g_strerror(errno));
|
|
goto error;
|
|
} else if (sel_ret > 0) {
|
|
b = cap_pipe_read(fd, ((char *)&magic)+bytes_read,
|
|
sizeof magic-bytes_read,
|
|
pcap_src->from_cap_socket);
|
|
/* jump messaging, if extcap had an error, stderr will provide the correct message */
|
|
if (extcap_pipe && b <= 0)
|
|
goto error;
|
|
|
|
if (b <= 0) {
|
|
if (b == 0)
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"End of file on pipe magic during open.");
|
|
else
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Error on pipe magic during open: %s.",
|
|
g_strerror(errno));
|
|
goto error;
|
|
}
|
|
bytes_read += b;
|
|
}
|
|
}
|
|
}
|
|
#ifdef _WIN32
|
|
else {
|
|
/* Create a thread to read from this pipe */
|
|
g_thread_new("cap_pipe_open_live", &cap_thread_read, pcap_src);
|
|
|
|
pipe_read_sync(pcap_src, &magic, sizeof(magic));
|
|
/* jump messaging, if extcap had an error, stderr will provide the correct message */
|
|
if (pcap_src->cap_pipe_bytes_read <= 0 && extcap_pipe)
|
|
goto error;
|
|
|
|
if (pcap_src->cap_pipe_bytes_read <= 0) {
|
|
if (pcap_src->cap_pipe_bytes_read == 0)
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"End of file on pipe magic during open.");
|
|
else
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Error on pipe magic during open: %s.",
|
|
g_strerror(errno));
|
|
goto error;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
switch (magic) {
|
|
case PCAP_MAGIC:
|
|
case PCAP_NSEC_MAGIC:
|
|
/* This is a pcap file.
|
|
The host that wrote it has our byte order, and was running
|
|
a program using either standard or ss990417 libpcap. */
|
|
pcap_src->cap_pipe_info.pcap.byte_swapped = FALSE;
|
|
pcap_src->cap_pipe_modified = FALSE;
|
|
pcap_src->ts_nsec = magic == PCAP_NSEC_MAGIC;
|
|
break;
|
|
case PCAP_MODIFIED_MAGIC:
|
|
/* This is a pcap file.
|
|
The host that wrote it has our byte order, but was running
|
|
a program using either ss990915 or ss991029 libpcap. */
|
|
pcap_src->cap_pipe_info.pcap.byte_swapped = FALSE;
|
|
pcap_src->cap_pipe_modified = TRUE;
|
|
break;
|
|
case PCAP_SWAPPED_MAGIC:
|
|
case PCAP_SWAPPED_NSEC_MAGIC:
|
|
/* This is a pcap file.
|
|
The host that wrote it has a byte order opposite to ours,
|
|
and was running a program using either standard or
|
|
ss990417 libpcap. */
|
|
pcap_src->cap_pipe_info.pcap.byte_swapped = TRUE;
|
|
pcap_src->cap_pipe_modified = FALSE;
|
|
pcap_src->ts_nsec = magic == PCAP_SWAPPED_NSEC_MAGIC;
|
|
break;
|
|
case PCAP_SWAPPED_MODIFIED_MAGIC:
|
|
/* This is a pcap file.
|
|
The host that wrote it out has a byte order opposite to
|
|
ours, and was running a program using either ss990915
|
|
or ss991029 libpcap. */
|
|
pcap_src->cap_pipe_info.pcap.byte_swapped = TRUE;
|
|
pcap_src->cap_pipe_modified = TRUE;
|
|
break;
|
|
case BLOCK_TYPE_SHB:
|
|
/* This is a pcapng file. */
|
|
pcap_src->from_pcapng = TRUE;
|
|
pcap_src->cap_pipe_dispatch = pcapng_pipe_dispatch;
|
|
pcap_src->cap_pipe_info.pcapng.src_iface_to_global = g_array_new(FALSE, FALSE, sizeof(guint32));
|
|
global_capture_opts.use_pcapng = TRUE; /* we can only output in pcapng format */
|
|
break;
|
|
default:
|
|
/* Not a pcapng file, and either not a pcap type we know about
|
|
or not a pcap file, either. */
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"File type is neither a supported pcap nor pcapng format. (magic = 0x%08x)", magic);
|
|
snprintf(secondary_errmsg, (gulong)secondary_errmsgl, "%s",
|
|
not_our_bug);
|
|
goto error;
|
|
}
|
|
|
|
if (pcap_src->from_pcapng)
|
|
pcapng_pipe_open_live(fd, pcap_src, errmsg, errmsgl);
|
|
else
|
|
pcap_pipe_open_live(fd, pcap_src, (struct pcap_hdr *) hdr, errmsg, errmsgl,
|
|
secondary_errmsg, secondary_errmsgl);
|
|
|
|
return;
|
|
|
|
error:
|
|
ws_debug("cap_pipe_open_live: error %s", errmsg);
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
cap_pipe_close(fd, pcap_src->from_cap_socket);
|
|
pcap_src->cap_pipe_fd = -1;
|
|
#ifdef _WIN32
|
|
pcap_src->cap_pipe_h = INVALID_HANDLE_VALUE;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Read the part of the pcap file header that follows the magic
|
|
* number (we've already read the magic number).
|
|
*/
|
|
static void
|
|
pcap_pipe_open_live(int fd,
|
|
capture_src *pcap_src,
|
|
struct pcap_hdr *hdr,
|
|
char *errmsg, size_t errmsgl,
|
|
char *secondary_errmsg, size_t secondary_errmsgl)
|
|
{
|
|
size_t bytes_read;
|
|
ssize_t b;
|
|
int sel_ret;
|
|
|
|
/*
|
|
* We're reading from a pcap file. We've already read the magic
|
|
* number; read the rest of the header.
|
|
*
|
|
* (Note that struct pcap_hdr is a structure for the part of a
|
|
* pcap file header *following the magic number*; it does not
|
|
* include the magic number itself.)
|
|
*/
|
|
#ifdef _WIN32
|
|
if (pcap_src->from_cap_socket)
|
|
#endif
|
|
{
|
|
/* Keep reading until we get the rest of the header. */
|
|
bytes_read = 0;
|
|
while (bytes_read < sizeof(struct pcap_hdr)) {
|
|
sel_ret = cap_pipe_select(fd);
|
|
if (sel_ret < 0) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Unexpected error from select: %s.",
|
|
g_strerror(errno));
|
|
goto error;
|
|
} else if (sel_ret > 0) {
|
|
b = cap_pipe_read(fd, ((char *)hdr)+bytes_read,
|
|
sizeof(struct pcap_hdr) - bytes_read,
|
|
pcap_src->from_cap_socket);
|
|
if (b <= 0) {
|
|
if (b == 0)
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"End of file on pipe header during open.");
|
|
else
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Error on pipe header during open: %s.",
|
|
g_strerror(errno));
|
|
snprintf(secondary_errmsg, (gulong)secondary_errmsgl,
|
|
"%s", not_our_bug);
|
|
goto error;
|
|
}
|
|
bytes_read += b;
|
|
}
|
|
}
|
|
}
|
|
#ifdef _WIN32
|
|
else {
|
|
pipe_read_sync(pcap_src, hdr, sizeof(struct pcap_hdr));
|
|
if (pcap_src->cap_pipe_bytes_read <= 0) {
|
|
if (pcap_src->cap_pipe_bytes_read == 0)
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"End of file on pipe header during open.");
|
|
else
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Error on pipe header header during open: %s.",
|
|
g_strerror(errno));
|
|
snprintf(secondary_errmsg, (gulong)secondary_errmsgl, "%s",
|
|
not_our_bug);
|
|
goto error;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (pcap_src->cap_pipe_info.pcap.byte_swapped) {
|
|
/* Byte-swap the header fields about which we care. */
|
|
hdr->version_major = GUINT16_SWAP_LE_BE(hdr->version_major);
|
|
hdr->version_minor = GUINT16_SWAP_LE_BE(hdr->version_minor);
|
|
hdr->snaplen = GUINT32_SWAP_LE_BE(hdr->snaplen);
|
|
hdr->network = GUINT32_SWAP_LE_BE(hdr->network);
|
|
}
|
|
pcap_src->linktype = hdr->network;
|
|
/* Pick the appropriate maximum packet size for the link type */
|
|
switch (pcap_src->linktype) {
|
|
|
|
case 231: /* DLT_DBUS */
|
|
pcap_src->cap_pipe_max_pkt_size = WTAP_MAX_PACKET_SIZE_DBUS;
|
|
break;
|
|
|
|
case 279: /* DLT_EBHSCR */
|
|
pcap_src->cap_pipe_max_pkt_size = WTAP_MAX_PACKET_SIZE_EBHSCR;
|
|
break;
|
|
|
|
case 249: /* DLT_USBPCAP */
|
|
pcap_src->cap_pipe_max_pkt_size = WTAP_MAX_PACKET_SIZE_USBPCAP;
|
|
break;
|
|
|
|
default:
|
|
pcap_src->cap_pipe_max_pkt_size = WTAP_MAX_PACKET_SIZE_STANDARD;
|
|
break;
|
|
}
|
|
|
|
if (hdr->version_major < 2) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"The old pcap format version %d.%d is not supported.",
|
|
hdr->version_major, hdr->version_minor);
|
|
snprintf(secondary_errmsg, (gulong)secondary_errmsgl, "%s",
|
|
not_our_bug);
|
|
goto error;
|
|
}
|
|
|
|
pcap_src->cap_pipe_fd = fd;
|
|
return;
|
|
|
|
error:
|
|
ws_debug("pcap_pipe_open_live: error %s", errmsg);
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
cap_pipe_close(fd, pcap_src->from_cap_socket);
|
|
pcap_src->cap_pipe_fd = -1;
|
|
#ifdef _WIN32
|
|
pcap_src->cap_pipe_h = INVALID_HANDLE_VALUE;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Synchronously read the fixed portion of the pcapng section header block
|
|
* (we've already read the pcapng block header).
|
|
*/
|
|
static int
|
|
pcapng_read_shb(capture_src *pcap_src,
|
|
char *errmsg,
|
|
size_t errmsgl)
|
|
{
|
|
pcapng_section_header_block_t shb;
|
|
|
|
#ifdef _WIN32
|
|
if (pcap_src->from_cap_socket)
|
|
#endif
|
|
{
|
|
pcap_src->cap_pipe_bytes_to_read = sizeof(pcapng_block_header_t) + sizeof(pcapng_section_header_block_t);
|
|
if (cap_pipe_read_data_bytes(pcap_src, errmsg, errmsgl) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
#ifdef _WIN32
|
|
else {
|
|
pipe_read_sync(pcap_src, pcap_src->cap_pipe_databuf + sizeof(pcapng_block_header_t),
|
|
sizeof(pcapng_section_header_block_t));
|
|
if (pcap_src->cap_pipe_bytes_read <= 0) {
|
|
if (pcap_src->cap_pipe_bytes_read == 0)
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"End of file reading from pipe or socket.");
|
|
else
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Error reading from pipe or socket: %s.",
|
|
g_strerror(errno));
|
|
return -1;
|
|
}
|
|
/* Continuing with STATE_EXPECT_DATA requires reading into cap_pipe_databuf at offset cap_pipe_bytes_read */
|
|
pcap_src->cap_pipe_bytes_read = sizeof(pcapng_block_header_t) + sizeof(pcapng_section_header_block_t);
|
|
}
|
|
#endif
|
|
memcpy(&shb, pcap_src->cap_pipe_databuf + sizeof(pcapng_block_header_t), sizeof(pcapng_section_header_block_t));
|
|
switch (shb.magic)
|
|
{
|
|
case PCAPNG_MAGIC:
|
|
ws_debug("pcapng SHB MAGIC");
|
|
break;
|
|
case PCAPNG_SWAPPED_MAGIC:
|
|
ws_debug("pcapng SHB SWAPPED MAGIC");
|
|
/*
|
|
* pcapng sources can contain all sorts of block types.
|
|
* Rather than add a bunch of complexity to this code (which is
|
|
* often privileged), punt and tell the user to swap bytes
|
|
* elsewhere.
|
|
*
|
|
* XXX - punting means that the Wireshark test suite must be
|
|
* modified to:
|
|
*
|
|
* 1) have both little-endian and big-endian versions of
|
|
* all pcapng files piped to dumpcap;
|
|
*
|
|
* 2) pipe the appropriate file to dumpcap, depending on
|
|
* the byte order of the host on which the tests are
|
|
* being run;
|
|
*
|
|
* as per comments in bug 15772 and 15754.
|
|
*
|
|
* Are we *really* certain that the complexity added would be
|
|
* significant enough to make adding it a security risk? And
|
|
* why would this code even be running with any elevated
|
|
* privileges if you're capturing from a pipe? We should not
|
|
* only have given up all additional privileges if we're reading
|
|
* from a pipe, we should give them up in such a fashion that
|
|
* we can't reclaim them.
|
|
*/
|
|
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
|
#define OUR_ENDIAN "big"
|
|
#define IFACE_ENDIAN "little"
|
|
#else
|
|
#define OUR_ENDIAN "little"
|
|
#define IFACE_ENDIAN "big"
|
|
#endif
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Interface %u is " IFACE_ENDIAN " endian but we're " OUR_ENDIAN " endian.",
|
|
pcap_src->interface_id);
|
|
return -1;
|
|
default:
|
|
/* Not a pcapng type we know about, or not pcapng at all. */
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Unrecognized pcapng format or not pcapng data.");
|
|
return -1;
|
|
}
|
|
|
|
pcap_src->cap_pipe_max_pkt_size = WTAP_MAX_PACKET_SIZE_STANDARD;
|
|
|
|
/* Setup state to capture any options following the section header block */
|
|
pcap_src->cap_pipe_state = STATE_EXPECT_DATA;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Save IDB blocks for playback whenever we change output files.
|
|
* Rewrite EPB and ISB interface IDs.
|
|
*/
|
|
static gboolean
|
|
pcapng_adjust_block(capture_src *pcap_src, const pcapng_block_header_t *bh, u_char *pd)
|
|
{
|
|
switch(bh->block_type) {
|
|
case BLOCK_TYPE_SHB:
|
|
{
|
|
if (global_ld.pcapng_passthrough) {
|
|
/*
|
|
* We have a single pcapng input. We pass the SHB through when
|
|
* writing a single output file and for the first ring buffer
|
|
* file. We need to save it for the second and subsequent ring
|
|
* buffer files.
|
|
*/
|
|
g_free(global_ld.saved_shb);
|
|
global_ld.saved_shb = (guint8 *) g_memdup2(pd, bh->block_total_length);
|
|
|
|
/*
|
|
* We're dealing with one section at a time, so we can (and must)
|
|
* get rid of our old IDBs.
|
|
*/
|
|
for (unsigned i = 0; i < global_ld.saved_idbs->len; i++) {
|
|
saved_idb_t *idb_source = &g_array_index(global_ld.saved_idbs, saved_idb_t, i);
|
|
g_free(idb_source->idb);
|
|
}
|
|
g_array_set_size(global_ld.saved_idbs, 0);
|
|
} else {
|
|
/*
|
|
* We have a new SHB from this capture source. We need to keep
|
|
* global_ld.saved_idbs intact, so we mark IDBs we previously
|
|
* collected from this source as deleted.
|
|
*/
|
|
for (unsigned i = 0; i < pcap_src->cap_pipe_info.pcapng.src_iface_to_global->len; i++) {
|
|
guint32 iface_id = g_array_index(pcap_src->cap_pipe_info.pcapng.src_iface_to_global, guint32, i);
|
|
saved_idb_t *idb_source = &g_array_index(global_ld.saved_idbs, saved_idb_t, iface_id);
|
|
ws_assert(idb_source->interface_id == pcap_src->interface_id);
|
|
g_free(idb_source->idb);
|
|
memset(idb_source, 0, sizeof(saved_idb_t));
|
|
idb_source->deleted = TRUE;
|
|
ws_debug("%s: deleted pcapng IDB %u", G_STRFUNC, iface_id);
|
|
}
|
|
}
|
|
g_array_set_size(pcap_src->cap_pipe_info.pcapng.src_iface_to_global, 0);
|
|
}
|
|
break;
|
|
case BLOCK_TYPE_IDB:
|
|
{
|
|
/*
|
|
* Always gather IDBs. We can remove them or mark them as deleted
|
|
* when we get a new SHB.
|
|
*/
|
|
saved_idb_t idb_source = { 0 };
|
|
idb_source.interface_id = pcap_src->interface_id;
|
|
idb_source.idb_len = bh->block_total_length;
|
|
idb_source.idb = (guint8 *) g_memdup2(pd, idb_source.idb_len);
|
|
g_array_append_val(global_ld.saved_idbs, idb_source);
|
|
guint32 iface_id = global_ld.saved_idbs->len - 1;
|
|
g_array_append_val(pcap_src->cap_pipe_info.pcapng.src_iface_to_global, iface_id);
|
|
ws_debug("%s: mapped pcapng IDB %u -> %u from source %u",
|
|
G_STRFUNC, pcap_src->cap_pipe_info.pcapng.src_iface_to_global->len - 1, iface_id, pcap_src->interface_id);
|
|
}
|
|
break;
|
|
case BLOCK_TYPE_EPB:
|
|
case BLOCK_TYPE_ISB:
|
|
{
|
|
if (global_ld.pcapng_passthrough) {
|
|
/* Our input and output interface IDs are the same. */
|
|
break;
|
|
}
|
|
/* The interface ID is the first 32-bit field after the BH for both EPBs and ISBs. */
|
|
guint32 iface_id;
|
|
memcpy(&iface_id, pd + sizeof(pcapng_block_header_t), 4);
|
|
if (iface_id < pcap_src->cap_pipe_info.pcapng.src_iface_to_global->len) {
|
|
memcpy(pd + sizeof(pcapng_block_header_t),
|
|
&g_array_index(pcap_src->cap_pipe_info.pcapng.src_iface_to_global, guint32, iface_id), 4);
|
|
} else {
|
|
ws_debug("%s: pcapng EPB or ISB interface id %u > max %u", G_STRFUNC, iface_id, pcap_src->cap_pipe_info.pcapng.src_iface_to_global->len);
|
|
return FALSE;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Read the part of the initial pcapng SHB following the block type
|
|
* (we've already read the block type).
|
|
*/
|
|
static void
|
|
pcapng_pipe_open_live(int fd,
|
|
capture_src *pcap_src,
|
|
char *errmsg,
|
|
size_t errmsgl)
|
|
{
|
|
guint32 type = BLOCK_TYPE_SHB;
|
|
pcapng_block_header_t *bh = &pcap_src->cap_pipe_info.pcapng.bh;
|
|
|
|
ws_debug("pcapng_pipe_open_live: fd %d", fd);
|
|
|
|
/*
|
|
* A pcapng block begins with the block type followed by the block
|
|
* total length; we've already read the block type, now read the
|
|
* block length.
|
|
*/
|
|
#ifdef _WIN32
|
|
/*
|
|
* On UN*X, we can use select() on pipes or sockets.
|
|
*
|
|
* On Windows, we can only use it on sockets; to do non-blocking
|
|
* reads from pipes, we currently do reads in a separate thread
|
|
* and use GLib asynchronous queues from the main thread to start
|
|
* read operations and to wait for them to complete.
|
|
*/
|
|
if (pcap_src->from_cap_socket)
|
|
#endif
|
|
{
|
|
memcpy(pcap_src->cap_pipe_databuf, &type, sizeof(guint32));
|
|
pcap_src->cap_pipe_bytes_read = sizeof(guint32);
|
|
pcap_src->cap_pipe_bytes_to_read = sizeof(pcapng_block_header_t);
|
|
pcap_src->cap_pipe_fd = fd;
|
|
if (cap_pipe_read_data_bytes(pcap_src, errmsg, errmsgl) < 0) {
|
|
goto error;
|
|
}
|
|
memcpy(bh, pcap_src->cap_pipe_databuf, sizeof(pcapng_block_header_t));
|
|
}
|
|
#ifdef _WIN32
|
|
else {
|
|
g_thread_new("cap_pipe_open_live", &cap_thread_read, pcap_src);
|
|
|
|
bh->block_type = type;
|
|
pipe_read_sync(pcap_src, &bh->block_total_length,
|
|
sizeof(bh->block_total_length));
|
|
if (pcap_src->cap_pipe_bytes_read <= 0) {
|
|
if (pcap_src->cap_pipe_bytes_read == 0)
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"End of file reading from pipe or socket.");
|
|
else
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Error reading from pipe or socket: %s.",
|
|
g_strerror(errno));
|
|
goto error;
|
|
}
|
|
pcap_src->cap_pipe_bytes_read = sizeof(pcapng_block_header_t);
|
|
memcpy(pcap_src->cap_pipe_databuf, bh, sizeof(pcapng_block_header_t));
|
|
pcap_src->cap_pipe_fd = fd;
|
|
}
|
|
#endif
|
|
if ((bh->block_total_length & 0x03) != 0) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"block_total_length read from pipe is %u, which is not a multiple of 4.",
|
|
bh->block_total_length);
|
|
goto error;
|
|
}
|
|
if (pcapng_read_shb(pcap_src, errmsg, errmsgl)) {
|
|
goto error;
|
|
}
|
|
|
|
return;
|
|
|
|
error:
|
|
ws_debug("pcapng_pipe_open_live: error %s", errmsg);
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
cap_pipe_close(fd, pcap_src->from_cap_socket);
|
|
pcap_src->cap_pipe_fd = -1;
|
|
#ifdef _WIN32
|
|
pcap_src->cap_pipe_h = INVALID_HANDLE_VALUE;
|
|
#endif
|
|
}
|
|
|
|
/* We read one record from the pipe, take care of byte order in the record
|
|
* header, write the record to the capture file, and update capture statistics. */
|
|
static int
|
|
pcap_pipe_dispatch(loop_data *ld, capture_src *pcap_src, char *errmsg, size_t errmsgl)
|
|
{
|
|
struct pcap_pkthdr phdr;
|
|
enum { PD_REC_HDR_READ, PD_DATA_READ, PD_PIPE_EOF, PD_PIPE_ERR,
|
|
PD_ERR } result;
|
|
#ifdef _WIN32
|
|
gpointer q_status;
|
|
#endif
|
|
ssize_t b;
|
|
guint new_bufsize;
|
|
pcap_pipe_info_t *pcap_info = &pcap_src->cap_pipe_info.pcap;
|
|
|
|
#ifdef LOG_CAPTURE_VERBOSE
|
|
ws_debug("pcap_pipe_dispatch");
|
|
#endif
|
|
|
|
switch (pcap_src->cap_pipe_state) {
|
|
|
|
case STATE_EXPECT_REC_HDR:
|
|
#ifdef _WIN32
|
|
if (g_mutex_trylock(pcap_src->cap_pipe_read_mtx)) {
|
|
#endif
|
|
|
|
pcap_src->cap_pipe_state = STATE_READ_REC_HDR;
|
|
pcap_src->cap_pipe_bytes_to_read = pcap_src->cap_pipe_modified ?
|
|
sizeof(struct pcaprec_modified_hdr) : sizeof(struct pcaprec_hdr);
|
|
pcap_src->cap_pipe_bytes_read = 0;
|
|
|
|
#ifdef _WIN32
|
|
pcap_src->cap_pipe_buf = (char *) &pcap_info->rechdr;
|
|
g_async_queue_push(pcap_src->cap_pipe_pending_q, pcap_src->cap_pipe_buf);
|
|
g_mutex_unlock(pcap_src->cap_pipe_read_mtx);
|
|
}
|
|
#endif
|
|
/* Fall through */
|
|
|
|
case STATE_READ_REC_HDR:
|
|
#ifdef _WIN32
|
|
if (pcap_src->from_cap_socket)
|
|
#endif
|
|
{
|
|
b = cap_pipe_read(pcap_src->cap_pipe_fd, ((char *)&pcap_info->rechdr)+pcap_src->cap_pipe_bytes_read,
|
|
pcap_src->cap_pipe_bytes_to_read - pcap_src->cap_pipe_bytes_read, pcap_src->from_cap_socket);
|
|
if (b <= 0) {
|
|
if (b == 0)
|
|
result = PD_PIPE_EOF;
|
|
else
|
|
result = PD_PIPE_ERR;
|
|
break;
|
|
}
|
|
#ifdef _WIN32
|
|
pcap_src->cap_pipe_bytes_read += (DWORD)b;
|
|
#else
|
|
pcap_src->cap_pipe_bytes_read += b;
|
|
#endif
|
|
}
|
|
#ifdef _WIN32
|
|
else {
|
|
q_status = g_async_queue_timeout_pop(pcap_src->cap_pipe_done_q, PIPE_READ_TIMEOUT);
|
|
if (pcap_src->cap_pipe_err == PIPEOF) {
|
|
result = PD_PIPE_EOF;
|
|
break;
|
|
} else if (pcap_src->cap_pipe_err == PIPERR) {
|
|
result = PD_PIPE_ERR;
|
|
break;
|
|
}
|
|
if (!q_status) {
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
if (pcap_src->cap_pipe_bytes_read < pcap_src->cap_pipe_bytes_to_read) {
|
|
/* There's still more of the pcap packet header to read. */
|
|
return 0;
|
|
}
|
|
result = PD_REC_HDR_READ;
|
|
break;
|
|
|
|
case STATE_EXPECT_DATA:
|
|
#ifdef _WIN32
|
|
if (g_mutex_trylock(pcap_src->cap_pipe_read_mtx)) {
|
|
#endif
|
|
|
|
pcap_src->cap_pipe_state = STATE_READ_DATA;
|
|
pcap_src->cap_pipe_bytes_to_read = pcap_info->rechdr.hdr.incl_len;
|
|
pcap_src->cap_pipe_bytes_read = 0;
|
|
|
|
#ifdef _WIN32
|
|
pcap_src->cap_pipe_buf = pcap_src->cap_pipe_databuf;
|
|
g_async_queue_push(pcap_src->cap_pipe_pending_q, pcap_src->cap_pipe_buf);
|
|
g_mutex_unlock(pcap_src->cap_pipe_read_mtx);
|
|
}
|
|
#endif
|
|
/* Fall through */
|
|
|
|
case STATE_READ_DATA:
|
|
#ifdef _WIN32
|
|
if (pcap_src->from_cap_socket)
|
|
#endif
|
|
{
|
|
b = cap_pipe_read(pcap_src->cap_pipe_fd,
|
|
pcap_src->cap_pipe_databuf+pcap_src->cap_pipe_bytes_read,
|
|
pcap_src->cap_pipe_bytes_to_read - pcap_src->cap_pipe_bytes_read,
|
|
pcap_src->from_cap_socket);
|
|
if (b <= 0) {
|
|
if (b == 0)
|
|
result = PD_PIPE_EOF;
|
|
else
|
|
result = PD_PIPE_ERR;
|
|
break;
|
|
}
|
|
#ifdef _WIN32
|
|
pcap_src->cap_pipe_bytes_read += (DWORD)b;
|
|
#else
|
|
pcap_src->cap_pipe_bytes_read += b;
|
|
#endif
|
|
}
|
|
#ifdef _WIN32
|
|
else {
|
|
|
|
q_status = g_async_queue_timeout_pop(pcap_src->cap_pipe_done_q, PIPE_READ_TIMEOUT);
|
|
if (pcap_src->cap_pipe_err == PIPEOF) {
|
|
result = PD_PIPE_EOF;
|
|
break;
|
|
} else if (pcap_src->cap_pipe_err == PIPERR) {
|
|
result = PD_PIPE_ERR;
|
|
break;
|
|
}
|
|
if (!q_status) {
|
|
return 0;
|
|
}
|
|
}
|
|
#endif /* _WIN32 */
|
|
if (pcap_src->cap_pipe_bytes_read < pcap_src->cap_pipe_bytes_to_read) {
|
|
/* There's still more of the pcap packet data to read. */
|
|
return 0;
|
|
}
|
|
result = PD_DATA_READ;
|
|
break;
|
|
|
|
default:
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"pcap_pipe_dispatch: invalid state");
|
|
result = PD_ERR;
|
|
|
|
} /* switch (pcap_src->cap_pipe_state) */
|
|
|
|
/*
|
|
* We've now read as much data as we were expecting, so process it.
|
|
*/
|
|
switch (result) {
|
|
|
|
case PD_REC_HDR_READ:
|
|
/*
|
|
* We've read the packet header, so we know the captured length,
|
|
* and thus the number of packet data bytes. Take care of byte order.
|
|
*/
|
|
cap_pipe_adjust_pcap_header(pcap_src->cap_pipe_info.pcap.byte_swapped, &pcap_info->hdr,
|
|
&pcap_info->rechdr.hdr);
|
|
if (pcap_info->rechdr.hdr.incl_len > pcap_src->cap_pipe_max_pkt_size) {
|
|
/*
|
|
* The record contains more data than the advertised/allowed in the
|
|
* pcap header, do not try to read more data (do not change to
|
|
* STATE_EXPECT_DATA) as that would not fit in the buffer and
|
|
* instead stop with an error.
|
|
*/
|
|
snprintf(errmsg, (gulong)errmsgl, "Frame %u too long (%d bytes)",
|
|
ld->packets_captured+1, pcap_info->rechdr.hdr.incl_len);
|
|
break;
|
|
}
|
|
|
|
if (pcap_info->rechdr.hdr.incl_len > pcap_src->cap_pipe_databuf_size) {
|
|
/*
|
|
* Grow the buffer to the packet size, rounded up to a power of
|
|
* 2.
|
|
*/
|
|
new_bufsize = pcap_info->rechdr.hdr.incl_len;
|
|
/*
|
|
* https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
|
*/
|
|
new_bufsize--;
|
|
new_bufsize |= new_bufsize >> 1;
|
|
new_bufsize |= new_bufsize >> 2;
|
|
new_bufsize |= new_bufsize >> 4;
|
|
new_bufsize |= new_bufsize >> 8;
|
|
new_bufsize |= new_bufsize >> 16;
|
|
new_bufsize++;
|
|
pcap_src->cap_pipe_databuf = (char*)g_realloc(pcap_src->cap_pipe_databuf, new_bufsize);
|
|
pcap_src->cap_pipe_databuf_size = new_bufsize;
|
|
}
|
|
|
|
if (pcap_info->rechdr.hdr.incl_len) {
|
|
/*
|
|
* The record has some data following the header, try
|
|
* to read it next time.
|
|
*/
|
|
pcap_src->cap_pipe_state = STATE_EXPECT_DATA;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* No data following the record header? Then no more data needs to be
|
|
* read and we will fallthrough and emit an empty packet.
|
|
*/
|
|
/* FALLTHROUGH */
|
|
|
|
case PD_DATA_READ:
|
|
/*
|
|
* We've read the full contents of the packet record.
|
|
* Fill in a "struct pcap_pkthdr", and process the packet.
|
|
*/
|
|
phdr.ts.tv_sec = pcap_info->rechdr.hdr.ts_sec;
|
|
phdr.ts.tv_usec = pcap_info->rechdr.hdr.ts_usec;
|
|
phdr.caplen = pcap_info->rechdr.hdr.incl_len;
|
|
phdr.len = pcap_info->rechdr.hdr.orig_len;
|
|
|
|
if (use_threads) {
|
|
capture_loop_queue_packet_cb((u_char *)pcap_src, &phdr, pcap_src->cap_pipe_databuf);
|
|
} else {
|
|
capture_loop_write_packet_cb((u_char *)pcap_src, &phdr, pcap_src->cap_pipe_databuf);
|
|
}
|
|
|
|
/*
|
|
* Now we want to read the next packet's header.
|
|
*/
|
|
pcap_src->cap_pipe_state = STATE_EXPECT_REC_HDR;
|
|
return 1;
|
|
|
|
case PD_PIPE_EOF:
|
|
pcap_src->cap_pipe_err = PIPEOF;
|
|
return -1;
|
|
|
|
case PD_PIPE_ERR:
|
|
snprintf(errmsg, (gulong)errmsgl, "Error reading from pipe: %s",
|
|
#ifdef _WIN32
|
|
win32strerror(GetLastError()));
|
|
#else
|
|
g_strerror(errno));
|
|
#endif
|
|
/* Fall through */
|
|
case PD_ERR:
|
|
break;
|
|
}
|
|
|
|
pcap_src->cap_pipe_err = PIPERR;
|
|
/* Return here rather than inside the switch to prevent GCC warning */
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
pcapng_pipe_dispatch(loop_data *ld, capture_src *pcap_src, char *errmsg, size_t errmsgl)
|
|
{
|
|
enum { PD_REC_HDR_READ, PD_DATA_READ, PD_PIPE_EOF, PD_PIPE_ERR,
|
|
PD_ERR } result;
|
|
#ifdef _WIN32
|
|
gpointer q_status;
|
|
#endif
|
|
guint new_bufsize;
|
|
pcapng_block_header_t *bh = &pcap_src->cap_pipe_info.pcapng.bh;
|
|
|
|
#ifdef LOG_CAPTURE_VERBOSE
|
|
ws_debug("pcapng_pipe_dispatch");
|
|
#endif
|
|
|
|
switch (pcap_src->cap_pipe_state) {
|
|
|
|
case STATE_EXPECT_REC_HDR:
|
|
#ifdef LOG_CAPTURE_VERBOSE
|
|
ws_debug("pcapng_pipe_dispatch STATE_EXPECT_REC_HDR");
|
|
#endif
|
|
#ifdef _WIN32
|
|
if (g_mutex_trylock(pcap_src->cap_pipe_read_mtx)) {
|
|
#endif
|
|
|
|
pcap_src->cap_pipe_state = STATE_READ_REC_HDR;
|
|
pcap_src->cap_pipe_bytes_to_read = sizeof(pcapng_block_header_t);
|
|
pcap_src->cap_pipe_bytes_read = 0;
|
|
|
|
#ifdef _WIN32
|
|
if (!pcap_src->from_cap_socket) {
|
|
pcap_src->cap_pipe_buf = pcap_src->cap_pipe_databuf;
|
|
g_async_queue_push(pcap_src->cap_pipe_pending_q, pcap_src->cap_pipe_buf);
|
|
}
|
|
g_mutex_unlock(pcap_src->cap_pipe_read_mtx);
|
|
}
|
|
#endif
|
|
/* Fall through */
|
|
|
|
case STATE_READ_REC_HDR:
|
|
#ifdef LOG_CAPTURE_VERBOSE
|
|
ws_debug("pcapng_pipe_dispatch STATE_READ_REC_HDR");
|
|
#endif
|
|
#ifdef _WIN32
|
|
if (pcap_src->from_cap_socket) {
|
|
#endif
|
|
if (cap_pipe_read_data_bytes(pcap_src, errmsg, errmsgl) < 0) {
|
|
return -1;
|
|
}
|
|
#ifdef _WIN32
|
|
} else {
|
|
q_status = g_async_queue_timeout_pop(pcap_src->cap_pipe_done_q, PIPE_READ_TIMEOUT);
|
|
if (pcap_src->cap_pipe_err == PIPEOF) {
|
|
result = PD_PIPE_EOF;
|
|
break;
|
|
} else if (pcap_src->cap_pipe_err == PIPERR) {
|
|
result = PD_PIPE_ERR;
|
|
break;
|
|
}
|
|
if (!q_status) {
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
if (pcap_src->cap_pipe_bytes_read < pcap_src->cap_pipe_bytes_to_read) {
|
|
/* There's still more of the pcapng block header to read. */
|
|
return 0;
|
|
}
|
|
memcpy(bh, pcap_src->cap_pipe_databuf, sizeof(pcapng_block_header_t));
|
|
result = PD_REC_HDR_READ;
|
|
break;
|
|
|
|
case STATE_EXPECT_DATA:
|
|
#ifdef LOG_CAPTURE_VERBOSE
|
|
ws_debug("pcapng_pipe_dispatch STATE_EXPECT_DATA");
|
|
#endif
|
|
#ifdef _WIN32
|
|
if (g_mutex_trylock(pcap_src->cap_pipe_read_mtx)) {
|
|
#endif
|
|
pcap_src->cap_pipe_state = STATE_READ_DATA;
|
|
pcap_src->cap_pipe_bytes_to_read = bh->block_total_length;
|
|
|
|
#ifdef _WIN32
|
|
if (!pcap_src->from_cap_socket) {
|
|
pcap_src->cap_pipe_bytes_to_read -= pcap_src->cap_pipe_bytes_read;
|
|
pcap_src->cap_pipe_buf = pcap_src->cap_pipe_databuf + pcap_src->cap_pipe_bytes_read;
|
|
pcap_src->cap_pipe_bytes_read = 0;
|
|
g_async_queue_push(pcap_src->cap_pipe_pending_q, pcap_src->cap_pipe_buf);
|
|
}
|
|
g_mutex_unlock(pcap_src->cap_pipe_read_mtx);
|
|
}
|
|
#endif
|
|
/* Fall through */
|
|
|
|
case STATE_READ_DATA:
|
|
#ifdef LOG_CAPTURE_VERBOSE
|
|
ws_debug("pcapng_pipe_dispatch STATE_READ_DATA");
|
|
#endif
|
|
#ifdef _WIN32
|
|
if (pcap_src->from_cap_socket) {
|
|
#endif
|
|
if (cap_pipe_read_data_bytes(pcap_src, errmsg, errmsgl) < 0) {
|
|
return -1;
|
|
}
|
|
#ifdef _WIN32
|
|
} else {
|
|
|
|
q_status = g_async_queue_timeout_pop(pcap_src->cap_pipe_done_q, PIPE_READ_TIMEOUT);
|
|
if (pcap_src->cap_pipe_err == PIPEOF) {
|
|
result = PD_PIPE_EOF;
|
|
break;
|
|
} else if (pcap_src->cap_pipe_err == PIPERR) {
|
|
result = PD_PIPE_ERR;
|
|
break;
|
|
}
|
|
if (!q_status) {
|
|
return 0;
|
|
}
|
|
}
|
|
#endif /* _WIN32 */
|
|
if (pcap_src->cap_pipe_bytes_read < pcap_src->cap_pipe_bytes_to_read) {
|
|
/* There's still more of the pcap block contents to read. */
|
|
return 0;
|
|
}
|
|
result = PD_DATA_READ;
|
|
break;
|
|
|
|
default:
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"pcapng_pipe_dispatch: invalid state");
|
|
result = PD_ERR;
|
|
|
|
} /* switch (pcap_src->cap_pipe_state) */
|
|
|
|
/*
|
|
* We've now read as much data as we were expecting, so process it.
|
|
*/
|
|
switch (result) {
|
|
|
|
case PD_REC_HDR_READ:
|
|
/*
|
|
* We've read the pcapng block header, so we know the block type
|
|
* and length.
|
|
*/
|
|
if (bh->block_type == BLOCK_TYPE_SHB) {
|
|
/*
|
|
* We need to read the fixed portion of the SHB before to
|
|
* get the endianness before we can interpret the block length.
|
|
* (The block type of the SHB is byte-order-independent, so that
|
|
* an SHB can be recognized before we know the endianness of
|
|
* the section.)
|
|
*
|
|
* Continue the read process.
|
|
*/
|
|
pcapng_read_shb(pcap_src, errmsg, errmsgl);
|
|
return 1;
|
|
}
|
|
|
|
if ((bh->block_total_length & 0x03) != 0) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"Total length of pcapng block read from pipe is %u, which is not a multiple of 4.",
|
|
bh->block_total_length);
|
|
break;
|
|
}
|
|
if (bh->block_total_length > pcap_src->cap_pipe_max_pkt_size) {
|
|
/*
|
|
* The record contains more data than the advertised/allowed in the
|
|
* pcapng header, do not try to read more data (do not change to
|
|
* STATE_EXPECT_DATA) as that would not fit in the buffer and
|
|
* instead stop with an error.
|
|
*/
|
|
snprintf(errmsg, (gulong)errmsgl, "Frame %u too long (%d bytes)",
|
|
ld->packets_captured+1, bh->block_total_length);
|
|
break;
|
|
}
|
|
|
|
if (bh->block_total_length > pcap_src->cap_pipe_databuf_size) {
|
|
/*
|
|
* Grow the buffer to the packet size, rounded up to a power of
|
|
* 2.
|
|
*/
|
|
new_bufsize = bh->block_total_length;
|
|
/*
|
|
* https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
|
*/
|
|
new_bufsize--;
|
|
new_bufsize |= new_bufsize >> 1;
|
|
new_bufsize |= new_bufsize >> 2;
|
|
new_bufsize |= new_bufsize >> 4;
|
|
new_bufsize |= new_bufsize >> 8;
|
|
new_bufsize |= new_bufsize >> 16;
|
|
new_bufsize++;
|
|
pcap_src->cap_pipe_databuf = (guchar*)g_realloc(pcap_src->cap_pipe_databuf, new_bufsize);
|
|
pcap_src->cap_pipe_databuf_size = new_bufsize;
|
|
}
|
|
|
|
/* The record always has at least the block total length following the header */
|
|
if (bh->block_total_length < sizeof(pcapng_block_header_t)+sizeof(guint32)) {
|
|
snprintf(errmsg, (gulong)errmsgl,
|
|
"malformed pcapng block_total_length < minimum");
|
|
pcap_src->cap_pipe_err = PIPEOF;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Now we want to read the block contents.
|
|
*/
|
|
pcap_src->cap_pipe_state = STATE_EXPECT_DATA;
|
|
return 0;
|
|
|
|
case PD_DATA_READ:
|
|
/*
|
|
* We've read the full contents of the block.
|
|
* Process the block.
|
|
*/
|
|
if (use_threads) {
|
|
capture_loop_queue_pcapng_cb(pcap_src, bh, pcap_src->cap_pipe_databuf);
|
|
} else {
|
|
capture_loop_write_pcapng_cb(pcap_src, bh, pcap_src->cap_pipe_databuf);
|
|
}
|
|
|
|
/*
|
|
* Now we want to read the next block's header.
|
|
*/
|
|
pcap_src->cap_pipe_state = STATE_EXPECT_REC_HDR;
|
|
return 1;
|
|
|
|
case PD_PIPE_EOF:
|
|
pcap_src->cap_pipe_err = PIPEOF;
|
|
return -1;
|
|
|
|
case PD_PIPE_ERR:
|
|
snprintf(errmsg, (gulong)errmsgl, "Error reading from pipe: %s",
|
|
#ifdef _WIN32
|
|
win32strerror(GetLastError()));
|
|
#else
|
|
g_strerror(errno));
|
|
#endif
|
|
/* Fall through */
|
|
case PD_ERR:
|
|
break;
|
|
}
|
|
|
|
pcap_src->cap_pipe_err = PIPERR;
|