1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging
6import os
7import subprocess
8
9from autotest_lib.client.bin import utils
10
11class Tcpdump(object):
12    """tcpdump capture process wrapper."""
13
14    def __init__(self, iface, dumpfilename):
15        """Launches a tcpdump process on the background.
16
17        @param iface: The name of the interface to listen on.
18        @param dumpfilename: The filename of the destination dump file.
19        @raise utils.TimeoutError if tcpdump fails to start after 10 seconds.
20        """
21        logging.debug('Recording %s traffic to %s.', iface, dumpfilename)
22        # Force to run tcpdump as root, since the dump file is created *after*
23        # the process drops to a unprivileged user, meaning that it can't create
24        # the passed dumpfilename file.
25        self._tcpdump_proc = subprocess.Popen(
26                ['tcpdump', '-i', iface, '-w', dumpfilename, '-Z', 'root'],
27                stdout=open('/dev/null', 'w'),
28                stderr=subprocess.STDOUT)
29        # Wait for tcpdump to initialize and create the dump file.
30        utils.poll_for_condition(
31                lambda: os.path.exists(dumpfilename),
32                desc='tcpdump creates the dump file.',
33                sleep_interval=1,
34                timeout=10.)
35
36
37    def stop(self, timeout=10.):
38        """Stop the dump process and wait for it to return.
39
40        This method stops the tcpdump process running in background and waits
41        for it to finish for a given timeout.
42        @param timeout: The time to wait for the tcpdump to finish in seconds.
43                        None means no timeout.
44        @return whether the tcpdump is not running.
45        """
46        if not self._tcpdump_proc:
47            return True
48
49        # Send SIGTERM to tcpdump.
50        try:
51            self._tcpdump_proc.terminate()
52        except OSError, e:
53            # If the process exits before we can send it a SIGTERM, an
54            # OSError exception is raised here which we can ignore since the
55            # process already finished.
56            logging.error('Trying to kill tcpdump (%d): %s',
57                          self._tcpdump_proc.pid, e.strerror)
58
59        logging.debug('Waiting for pid %d to finish.', self._tcpdump_proc.pid)
60        if timeout is None:
61            self._tcpdump_proc.wait()
62        else:
63            try:
64                utils.poll_for_condition(
65                        lambda: not self._tcpdump_proc.poll() is None,
66                        sleep_interval=1,
67                        timeout=timeout)
68            except utils.TimeoutError:
69                logging.error('tcpdump failed to finish after %f seconds. Dump '
70                              'file can be truncated.', timeout)
71                return False
72
73        self._tcpdump_proc = None
74        return True
75
76
77    def __del__(self):
78        self.stop()
79