ktorn
ktorn

Reputation: 1052

Python: handling interrupts whilst running subprocess

I wrote a simple Python parser for tcpdump. The idea is to continuously run tcpdump as a subprocess, parse its output and output basic reports when the user requests them (by Ctrl-Z interrupts) without stopping the script.

A Ctrl-C should also output the report and totally quit the script, and that works.

The problem is when I press Ctrl-Z, the interrupt handler is called, it outputs tcpdump_output as expected, but then the script stops processing the output of the tcpdump subprocess, even though it is still running in the background (I checked with ps).

A simplified version of the script:

#!/usr/bin/env python

import subprocess as sub
import socket
import signal
import sys

tcpdump_output = ""

def signal_handler(sig, frame):
    print('\nInterrupt detected. Output:')

    print(tcpdump_output)

    if(sig is signal.SIGINT):
        print('Terminated.')
        sys.exit(0)


def process_tcpdump_line(line):
    print("processing tcpdump line: " + line)
    global tcpdump_output
    tcpdump_output += line + "\n"


# get host ip address
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
local_ip = s.getsockname()[0]
s.close()

# register interrupt handlers
signal.signal(signal.SIGINT, signal_handler)    # Handle Ctrl-C
signal.signal(signal.SIGTSTP, signal_handler)   # Handle Ctrl-Z


# prepare tcpdump command
dst = 'dst host ' + local_ip
p = sub.Popen(('sudo', 'tcpdump', '-nqnn', '-l', dst), stdout=sub.PIPE)


# process tcpdump output
for row in iter(p.stdout.readline, b''):
     process_tcpdump_line(row.strip())

print("this is never reached.")

I tried wrapping the for-loop in a while True but when debugging I see that it doesn't make a difference, the last line is really never reached.

So seems like after the handled interrupt, the script hangs on p.stdout.readline.

Is there a way to handle the interrupt without affecting the subprocess, or otherwise resume its processing?

Upvotes: 3

Views: 779

Answers (1)

Nickolay
Nickolay

Reputation: 32063

If you check ps aux output you'll see that the STAT column of the tcpdump process changes after you press CTRL+Z:

Before CTRL+Z:

USER              PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
root            15064   0.0  0.0  2463988   1096 s010  S+    4:40PM   0:00.01 tcpdump -nqnn -l dst host 192.168.1.169
root            15063   0.0  0.0  2461648   2004 s010  S+    4:40PM   0:00.01 sudo tcpdump -nqnn -l dst host 192.168.1.169
nickolay        15062   0.0  0.0  2432140   7136 s010  S+    4:40PM   0:00.04 /opt/local/Library/Frameworks/Python.framework/Versions/3.7/Resources/Python.app/Contents/MacOS/Python tcpd.py

After CTRL+Z:

USER              PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
root            15064   0.0  0.0  2463988   1096 s010  T+    4:40PM   0:00.01 tcpdump -nqnn -l dst host 192.168.1.169
root            15063   0.0  0.0  2461648   2004 s010  T+    4:40PM   0:00.01 sudo tcpdump -nqnn -l dst host 192.168.1.169
nickolay        15062   0.0  0.0  2432140   7212 s010  S+    4:40PM   0:00.04 /opt/local/Library/Frameworks/Python.framework/Ver

The T status means "stopped, either by a job control signal or because it is being traced."

So the problem is that the child process handles CTRL+Z as well and is "stopped by job control".

To avoid this, ignore the signal in the child process:

p = sub.Popen(('sudo', 'tcpdump', '-nqnn', '-l', dst), stdout=sub.PIPE,
            preexec_fn = lambda: signal.signal(signal.SIGTSTP, signal.SIG_IGN))

Upvotes: 2

Related Questions