【问题标题】:subprocess.Popen(): change stderr during child's executionsubprocess.Popen():在子进程执行期间更改标准错误
【发布时间】:2020-12-04 08:34:51
【问题描述】:

目标:我正在尝试将 Python 脚本放在一起,以捕获由于执行代码块而发生的网络流量。为简单起见,假设我想记录调用socket.gethostbyname('example.com') 产生的网络流量。注意:当gethostbyname() 返回时,我不能简单地终止tcpdump,因为我要测量的实际代码块会触发其他外部代码,并且我无法确定此外部代码何时完成执行(所以我必须让tcpdump 运行“足够长”,以便我很可能记录了此外部代码生成的所有流量。

方法:我使用subprocess 启动tcpdump,告诉tcpdumpduration 秒后使用其-G-W 选项终止自身,例如:

duration = 15
nif = 'en0'
pcap = 'dns.pcap'
cmd = ['tcpdump', '-G', str(duration), '-W', '1', '-i', nif, '-w', pcap]
tcpdump_proc = subprocess.Popen(cmd)
socket.gethostbyname('example.com')
time.sleep(duration + 5) # sleep longer than tcpdump is running

问题在于Popen() 返回之前 tcpdump 完全启动并运行,因此调用gethostbyname() 产生的部分/全部流量将不会被捕获。我显然可以在调用gethostbyname() 之前添加一个time.sleep(x)tcpdump 一点时间来启动,但这不是一个可移植的解决方案(我不能随便选择一些x < duration,因为一个强大的系统会启动比功能较弱的系统更早地捕获数据包)。

为了解决这个问题,我的想法是解析tcpdump 的输出以查找何时将以下内容写入其stderr,因为这似乎表明捕获已启动并完全运行:

tcpdump: listening on en0, link-type EN10MB (Ethernet), capture size 262144 bytes

因此我需要附加到stderr,但问题是我不想承诺读取它的所有输出,因为我需要我的代码继续执行我想要测量的代码块( gethostbyname() 在这个例子中)而不是陷入从 stderr 读取的循环中。

我可以通过添加一个信号量来解决这个问题,该信号量阻止主线程继续进行gethostbyname() 调用,并从stderr 读取一个后台线程,并在它时减少信号量(让主线程继续运行)从stderr 读取上面的字符串,但如果可能,我想保持代码单线程。

据我了解,将subprocess.PIPE 用于stderrstdout 而不承诺读取所有输出是一个很大的NONO,因为当缓冲区填满时,孩子最终会阻塞。但是,如果您只对读取输出的第一部分感兴趣,您可以“分离”(销毁?)管道中间执行吗?基本上我想得到这样的结果:

duration = 15
nif = 'en0'
pcap = 'dns.pcap'
cmd = ['tcpdump', '-G', str(duration), '-W', '1', '-i', nif, '-w', pcap]
tcpdump_proc = subprocess.Popen(cmd, stderr=subprocess.PIPE, text=True)
for l in tcpdump_proc.stderr:
    if 'tcpdump: listening on' in l:
        break
socket.gethostbyname('example.com')
time.sleep(duration) # sleep at least as long as tcpdump is running

我还需要在if 块中添加什么来“重新分配”负责阅读stderr 的人?我可以将stderr 设置回None (tcpdump_proc.stderr = None) 吗?或者我应该打电话给tcpdump_proc.stderr.close()(如果我这样做,tcpdump 会提前终止吗)?

也很可能是我错过了一些明显的东西,并且有更好的方法来实现我想要的 - 如果是这样,请赐教:)。

提前致谢:)

【问题讨论】:

  • 为什么不在 dns.pcap 捕获文件创建后继续?这发生在 “正在侦听” 消息写入stderr 之后,因此它发生在更接近tcpdump 完全准备好时。作为奖励,您也不必担心写给stderr“正在监听” 消息的确切文本是什么,所以如果它应该改变,那将是无关紧要的给你。请务必在启动tcpdump 之前删除捕获文件,以防文件已存在。
  • @ChristopherMaynard 太好了,非常感谢您提供这些信息!我假设文件将首先创建(以便在某个地方存储数据包)之前 tcpdump 开始侦听数据包(并打印“正在侦听”)。你怎么知道它是相反的?从源代码?如果是这样,您介意分享一下具体在哪里吗(我自己找不到,抱歉)?
  • 是的,我看了tcpdump.org/index.html#latest-releases的源码
  • 好的,我实际上看过一个较旧的 4.1.1。发布。最新的 4.9.3 略有不同,现在似乎在打开捕获文件之后打印了“正在侦听”消息,我猜这是您所期望的。不过,如果您等待创建文件,我认为这应该足以满足您的目的。好吧,如果您想探索它,这是一个选择。

标签: python subprocess pipe tcpdump


【解决方案1】:

收到listening on 消息后,您可以在stderr 上使用detach()close()

import subprocess
import time

duration = 10
nif = 'eth0'
pcap = 'dns.pcap'
cmd = ['tcpdump', '-G', str(duration), '-W', '1', '-i', nif, '-w', pcap]

proc = subprocess.Popen(
    cmd, shell=False, stderr=subprocess.PIPE, bufsize=1, text=True
)
for i, line in enumerate(proc.stderr):
    print('read %d lines from stderr' % i)
    if 'listening on' in line:
        print('detach stderr!')
        proc.stderr.detach()
        break

while proc.poll() is None:
    print("doing something else while tcpdump is runnning!")
    time.sleep(2)

print(proc.returncode)
print(proc.stderr.read())

输出:

read 0 lines from stderr
detach stderr!
doing something else while tcpdump is runnning!
doing something else while tcpdump is runnning!
doing something else while tcpdump is runnning!
doing something else while tcpdump is runnning!
doing something else while tcpdump is runnning!
doing something else while tcpdump is runnning!
0
Traceback (most recent call last):
  File "x.py", line 24, in <module>
    print(proc.stderr.read())
ValueError: underlying buffer has been detached

注意:

我还没有检查 stderr 数据真正发生了什么,但是分离 stderr 似乎对 tcpdump 没有任何影响。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-30
    • 2011-05-19
    • 1970-01-01
    • 2021-09-03
    • 2011-08-10
    相关资源
    最近更新 更多