Search code examples
pythonprocesssubprocesstcpdump

Python subprocess: stderr only saving the first line. Why?


I am running tcpdump from within Python and I would like to know how many packets are dropped by the kernel.

When run on a command line, tcpdump looks like this:

me@mypc:$ sudo tcpdump -w myPackets.cap -i eth0 ip
tcpdump: listening on eth2, link-type EN10MB (Ethernet), capture size 65535 bytes
^C28 packets captured
28 packets received by filter
0 packets dropped by kernel

This is how I call tcpdump in my Python script:

f_out = open("tcpdumpSTDOUT", "w")
f_err = open("tcpdumpSTDERR", "w")
tcpdumpProcess = subprocess.Popen(['tcpdump',
                        '-w', 'myPackets.cap', '-i', 'eth0', '-n','ip'],
                        stdout=f_out,
                        stderr=f_err)
# a few seconds later:
tcpdumpProcess.kill()
f_in.close()
f_out.close()

Now, if I look at tcpdumpSTDERR, I only see the first of the usual output lines:

tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes

Where's all the rest?

EDIT I tried a different approach:

>>> myProcess = subprocess.Popen("tcpdump -w myPackets.cap -i eth2 ip",  shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>>> myProcess.communicate()

Then I killed tcpdump from a different shell, and the output of commnunicate() was displayed:

('', 'tcpdump: listening on eth2, link-type EN10MB (Ethernet), capture size 65535 bytes\n')

... still the first line only!

EDIT 2 Interestingly:

>>> import shlex
>>> a = subprocess.Popen(shlex.split("tcpdump -w myPackets.cap -i eth2 ip"),  stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>>> a.terminate()
>>> a.communicate()
('', 'tcpdump: listening on eth2, link-type EN10MB (Ethernet), capture size 65535 bytes\n221 packets captured\n221 packets received by filter\n0 packets dropped by kernel\n')

Solution

  • The problem was that I called kill() on the process instead of terminate(). With the latter, all messages get stored in whatever I specified as stderr (tcpdump, for some reason, writes to stderr and not to stdout).

    So, in case it might help others, I decided to redirect stderr to subprocess.PIPE and parse the string directly in Python:

    >>> tcpdumpProcess = subprocess.Popen(['tcpdump',
                            '-w', 'myPackets.cap', '-i', 'eth0', '-n','ip'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)
    >>> tcpdumpProcess.terminate()
    # stdout in [0], stderr in [1]
    >>> tcpdump_stderr = tcpdumpProcess.communicate()[1]
    >>> print tcpdump_stderr
    tcpdump: listening on eth2, link-type EN10MB (Ethernet), capture size 65535 bytes
    40 packets captured
    40 packets received by filter
    0 packets dropped by kernel