【问题标题】:Write tcpdump output to compressed / gziped file将 tcpdump 输出写入压缩/gzip 文件
【发布时间】:2018-05-23 13:02:08
【问题描述】:

我想将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
# 

两个问题:

  1. “显而易见的解决方案”有什么问题?
  2. 如何在 Debian Jessie 中捕获和压缩 tcpdump 输出? (显而易见的解决方案也不起作用)

谢谢!

【问题讨论】:

  • 显而易见的解决方案是在流经管道的内容足够多的情况下工作。如果您只是想捕捉几行,或者需要在按下 ctrl+c 时捕捉到最后的所有内容,那么......是的,您在这里遇到了问题。 (但是,仅捕获非常少量的内容并不是通常需要内联 gzip 的情况)。
  • 顺便说一句,对于额外的偏执狂,您可以更改我对stdbuf -oL tcpdump 的回答中的tcpdumps,以告诉glibc 在每一行之后刷新tcpdump 的输出。但是,不应该需要,因为 SIGINT 是可捕获的(因此允许程序有机会自己进行最终刷新)。但是,如果使用 SIGKILL 发生关机,您将需要它。
  • ...顺便说一句,BashFAQ #9 可能是有用的补充阅读。
  • 感谢您的解释。我刚刚验证过了。 gzip 仅在压缩完整的 32k 块时写入文件。由于我最初的 tcpdump 输出包含许多类似的行,因此需要很长时间才能达到 32k。

标签: bash debian gzip tcpdump


【解决方案1】:

发生了什么

解释这里发生了什么:

  • Ctrl+C整个进程组发送一个 SIGINT。这意味着它不仅会终止tcpdump,还会终止gzip。 (您尝试的解决方法是通过将内容移到后台进程中来避免这种情况,从而移出同一个进程组)。
  • 默认情况下,stdout 仅在输出到 TTY 时才进行行缓冲;当输出到 FIFO 时,它是块缓冲的,只有在足够大的块可用时才通过从左侧进程写入数据来提高效率。在许多 的情况下,您可以使用stdbuf -oL 或类似的方法来禁用它。不过……
  • gzip 本质上不能完全无缓冲地运行。这是因为基于块的压缩算法需要将数据收集到中;批量分析该内容;等等。

因此,如果 gziptcpdump 同时终止,这意味着无法保证 tcpdump 实际上能够刷新其输出缓冲区,然后读取 gzip,在gzip 本身退出它同时收到的信号之前,压缩并写入刷新的数据。


解决问题

请注意,包含单词“Interactive”的标题下的代码 sn-ps 旨在用于交互式使用


可靠的交互式解决方法(适用于 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(通过杀死 only 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"

【讨论】:

    【解决方案2】:

    来自Charles Duffy's的回答(非常感谢他!):

    Ctrl+C整个进程组发送一个 SIGINT。这意味着它不仅会终止tcpdump,还会终止gzip。 (您尝试的解决方法是通过将内容移到后台进程中来避免这种情况,从而移出同一个进程组)。

    因为他是正确的,gzip 只有在完整的32k block 被压缩时才写入输出文件,所以我已经在一个终端中启动了“明显的解决方案”......

    $ 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
    1926 packets captured
    1938 packets received by filter
    0 packets dropped by kernel
    $ 
    

    并从第二个终端杀死 tcpdump:

    $ killall -INT tcpdump
    $
    

    在后台 tcpdump -l -i eth0 | gzip -c &gt; test.gz &amp; 中启动“明显的解决方案”将允许从同一终端终止 tcpdump。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-12
      • 1970-01-01
      • 1970-01-01
      • 2023-02-02
      • 2023-02-06
      相关资源
      最近更新 更多