#python #subprocess #pipe #tcpdump
#python #подпроцесс #канал #tcpdump
Вопрос:
Цель: я пытаюсь собрать скрипт на Python, который фиксирует сетевой трафик, возникающий в результате выполнения блока кода. Для простоты предположим, что я хочу зарегистрировать сетевой трафик, полученный в результате вызова socket.gethostbyname('example.com')
. Примечание: я не могу просто завершить tcpdump
when gethostbyname()
returns, поскольку фактический блок кода, который я хочу измерить, запускает другой внешний код, и у меня нет способа определить, когда этот внешний код завершает выполнение (поэтому я должен оставить tcpdump
запуск «достаточно долго», чтобы с высокой вероятностью я зарегистрировал весь трафикгенерируется этим внешним кодом).
Подход: я использую subprocess
для запуска tcpdump
, сообщая tcpdump
о завершении через duration
несколько секунд, используя его -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()
returns before tcpdump
полностью запущен и запущен, поэтому часть / весь трафик, полученный в результате вызова gethostbyname()
, не будет перехвачен. Очевидно, я мог бы добавить a time.sleep(x)
перед вызовом gethostbyname()
, чтобы дать tcpdump
немного времени для запуска, но это не переносимое решение (я не могу просто выбрать произвольное x < duration
, поскольку мощная система начнет захватывать пакеты раньше, чем менее мощная система).
Чтобы справиться с этим, моя идея состоит в том, чтобы проанализировать tcpdump
вывод, чтобы найти, когда в него записывается следующее stderr
, поскольку это указывает на то, что захват запущен и работает полностью:
tcpdump: listening on en0, link-type EN10MB (Ethernet), capture size 262144 bytes
Таким образом, мне нужно подключиться к stderr
, но проблема в том, что я не хочу фиксироваться на чтении всего его вывода, поскольку мне нужно, чтобы мой код двигался дальше, чтобы фактически выполнить блок кода, который я хочу измерить ( gethostbyname()
в этом примере), вместо того, чтобы застрять в цикле чтения stderr
.
Я мог бы решить эту проблему, добавив семафор, который блокирует переход основного потока к gethostbyname()
вызову, и заставить фоновый поток считывать stderr
и уменьшать семафор (чтобы позволить основному потоку двигаться дальше), когда он считывает строку выше stderr
, но я бы хотел, чтобы код был однопоточным, если это возможно.
Насколько я понимаю, это большой NONO для использования subprocess.PIPE
stderr
и stdout
без совершения чтения всего вывода, поскольку дочерний элемент в конечном итоге блокируется при заполнении буфера. Но можете ли вы «отделить» (уничтожить?) промежуточное выполнение канала, если вы заинтересованы только в чтении первой части выходных данных? По сути, я хотел бы в конечном итоге получить что-то вроде этого:
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
завершится досрочно, если я это сделаю)?
Также вполне может быть, что я пропустил что-то очевидное и что есть гораздо лучший подход для достижения того, чего я хочу — если да, пожалуйста, просветите меня :).
Заранее спасибо 🙂
Комментарии:
1. Почему бы просто не продолжить после создания файла захвата dns.pcap ? Это происходит после записи сообщения «прослушивание включено»
stderr
, поэтому оно происходит еще ближе к моментуtcpdump
полной готовности. В качестве бонуса вам также никогда не придется беспокоиться о том, каков точный текст сообщения «listening on»stderr
, поэтому, если он когда-либо изменится, для вас это будет несущественно. Просто не забудьте удалить файл захвата перед запускомtcpdump
, на случай, если файл уже существует.2.@ChristopherMaynard Это здорово, огромное спасибо за эту информацию! Я предполагал, что файл будет создан первым (чтобы иметь место для хранения пакетов), прежде
tcpdump
чем начнет прослушивать пакеты (и напечатает «прослушивание включено»). Как вы выяснили, что все наоборот? Из исходного кода? Если да, не могли бы вы поделиться, где именно, пожалуйста (я не смог найти его сам, извините)?3. Да, я просмотрел исходный код из tcpdump.org/index.html#latest-releases
4. Хорошо, я действительно просмотрел более старую версию 4.1.1. release. Последняя версия 4.9.3 немного отличается, и кажется, что сообщение «прослушивание включено» теперь печатается после открытия файла захвата, чего, я думаю, и следовало ожидать. Тем не менее, если вы дождетесь создания файла, я думаю, этого должно быть достаточно для ваших целей. Ну, это вариант на случай, если вы захотите его изучить.
Ответ №1:
Вы можете использовать detach()
или close()
в stderr после получения listening on
сообщения:
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())
Out:
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.