我想将 tcpdump
的文本输出写入压缩文件。
首先我尝试了最明显的:
# tcpdump -l -i eth0 | gzip -c > test.gz
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
^C63 packets captured
244 packets received by filter
0 packets dropped by kernel
4 packets dropped by interface
# file test.gz
test.gz: empty
#
然后我找到了以下针对 Debian 9 (Stretch)
的解决方案:
# tcpdump -l -i eth0 | ( gzip -c > test.gz & )
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
^C150 packets captured
160 packets received by filter
0 packets dropped by kernel
# file test.gz
test.gz: gzip compressed data, last modified: Wed May 23 12:56:16 2018, from Unix
#
这在 Debian 9 (Stretch)
上运行良好,但在 Debian 8 (Jessie)
上运行不佳:
# tcpdump -l -i eth0 | ( gzip -c > test.gz & )
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
tcpdump: Unable to write output: Broken pipe
#
两个问题:
- “显而易见的解决方案”有什么问题?
- 如何在 Debian Jessie 中捕获并压缩 tcpdump 输出? (明显的解决方案在那里也不起作用)
谢谢!
最佳答案
发生了什么
解释一下这里发生了什么:
- Ctrl+C 向整个进程组发送 SIGINT。这意味着它不仅仅终止
tcpdump
,但也终止gzip
。 (您尝试的解决方法是通过将内容移至后台进程,从而移出同一进程组来避免这种情况)。 - 仅当输出到 TTY 时,stdout 默认是行缓冲的;当输出到 FIFO 时,它是 block 缓冲的,只有在足够大的 block 可用时才从左侧进程写入数据,从而提高效率。在许多情况下,您可以只使用
stdbuf -oL
或类似的禁用此功能。然而... -
gzip
本质上无法完全无缓冲地运行。这是因为基于 block 的压缩算法需要将数据收集到 block 中;批量分析该内容;等等。
所以,如果 gzip
和tcpdump
同时终止,这意味着无法保证 tcpdump
实际上能够刷新其输出缓冲区,然后有 gzip
在 gzip
之前读取、压缩和写入刷新的数据本身从同时接收到的信号中退出。
解决问题
请注意,包含“Interactive”一词的标题下的代码片段用于交互式使用。
可靠的交互式解决方法(针对 Bash)
作为万无一失的解决方案,请将 gzip
移至完全在带外,因此当您在 tcpdump
上按 ctrl+c 时,不容易发送 SIGINT命令:
exec 3> >(gzip -c >test.gz) # Make FD 3 point to gzip
tcpdump -l -i eth0 >&3 # run tcpdump **AS A SEPARATE COMMAND** writing to that fd
exec 3>&- # later, after you cancelled tcpdump, close the FD.
可靠的交互式解决方法(适用于任何 POSIX Shell)
同样的事情,但稍微长一点并且不依赖进程替换:
mkfifo test.fifo # create a named FIFO
gzip -c <test.fifo >test.gz & gzip_pid="$!" # start gzip, reading from that named FIFO
tcpdump -l -i eth0 >test.fifo # start tcpdump, writing to that named FIFO
rm test.fifo # delete the FIFO when done
wait "$gzip_pid" # ...and wait for gzip to exit
请注意 wait
会有gzip进程的退出状态,因此您可以确定它是否遇到错误。
可靠的脚本解决方法(适用于任何 POSIX Shell)
如果我们正在运行脚本,那么最好设置一个信号处理程序,以便我们可以显式处理 SIGINT(通过仅杀死 tcpdump):
#!/bin/sh
[ "$#" -gt 0 ] || {
echo "Usage: ${0##*/} file.tcpdump.gz [tcpdump-args]" >&2
echo " Example: ${0##*/} foo.tcpdump.gz -l -i eth0" >&2
exit 1
}
outfile=$1; shift
fifo=test-$$.fifo # for real code, put this in a unique temporary directory
trap '[ -n "$tcpdump_pid" ] && kill "$tcpdump_pid"' INT
trap 'rm -f -- "$fifo"' EXIT
rm -f -- "$fifo"; mkfifo "$fifo" || exit
gzip -c >"$outfile" <"$fifo" & gzip_pid=$!
# avoid trying to run tcpdump if gzip obviously failed to start
{ [ -n "$gzip_pid" ] && [ "$gzip_pid" -gt 0 ] && kill -0 "$gzip_pid"; } || exit 1
tcpdump "$@" >"$fifo" & tcpdump_pid=$!
# return exit status of tcpdump if it fails, or gzip if tcpdump succeeds
wait "$tcpdump_pid" || wait "$gzip_pid"
关于bash - 将 tcpdump 输出写入压缩/gziped 文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50489159/