【Python实战】实时获取tcpdump输出

Python实时获取tcpdump输出。

一、背景

  今天有个小需求,要确认客户端有没有往服务端发送udp包,但为了减轻工作量,不想每次到机器上手动执行tcpdump抓包命令。
  于是就写了个脚本来释放人力。

二、代码实现

  整个脚本我还加了一些其他功能:时间戳、发送端IP提取,数据包分析,数据持久化等。这里都先去掉,仅记录下简单的实时获取tcpdump输出功能。
  代码如下:

# -*- coding: utf-8 -*-
# !/usr/bin/env python

# sudo tcpdump -tt -l -nn -c 5 -i enp4s0 udp port 514 or 51414

import subprocess

cmd = ['sudo', 'tcpdump', '-tt', '-l', '-nn', '-c', '5', '-i', 'enp4s0', 'udp', 'port', '514', 'or', '51414']
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)

while True:
    line = proc.stdout.readline()
    line = line.strip()
    if not line:
        print('tcpdump finished...')
        break
    print(line)

  输出如下(实时):

wenyuanblog@localhost:/home/test/script# python tcpdump_udp.py 
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp4s0, link-type EN10MB (Ethernet), capture size 262144 bytes
1499774951.124178 IP 192.168.10.210.41974 > 192.168.10.251.514: UDP, length 139
1499774953.125664 IP 192.168.10.210.54995 > 192.168.10.251.51414: UDP, length 139
1499774956.128498 IP 192.168.10.210.56748 > 192.168.10.251.514: UDP, length 139
1499774958.129918 IP 192.168.10.210.53883 > 192.168.10.251.51414: UDP, length 139
1499774961.132921 IP 192.168.10.210.58803 > 192.168.10.251.514: UDP, length 139
5 packets captured
6 packets received by filter
0 packets dropped by kernel
tcpdump finished...

  以上代码相当于手动执行了 sudo tcpdump -tt -l -nn -c 5 -i enp4s0 udp port 514 or 51414 这条命令。
  注意参数-l很重要(行显)。

三、代码实现(更新)

  上面的代码能实现tcpdump的功能,但是有一个问题:没有做超时保护。即当程序执行时间过长时kill该进程(这里使用ctrl+c的方式)。
  要实现这个功能有很多种方案,例如定时器+多线程等,这里仅演示一种方案,代码如下:

# -*- coding: utf-8 -*-
# !/usr/bin/env python

# sudo tcpdump -tt -l -nn -c 50 -i enp4s0 udp port 514 or 51414

import subprocess
import signal
import time
import os
import re
import json


class CmdServer:

    def __init__(self, cmd, timeout=120):
        '''
        :param cmd: 执行命令(列表形式)
        :param timeout: 任务超时时间(seconds,进程运行超过该时间,kill该进程)
        :param taskname: 任务名称(根据该任务名称记录命令输出信息)
        '''
        self.cmd = cmd
        self.timeout = timeout
        self.base_path = reduce(lambda x, y: os.path.dirname(x), range(1), os.path.abspath(__file__))
        self.output_path = os.path.join(self.base_path, 'data.json')
        self.udp_flow_list = []
        self.begin_time = int(time.time())

    # 执行tcpdump任务
    def run(self):
        if os.path.exists(self.output_path):
            with open(self.output_path, 'r') as f:
                self.udp_flow_list = json.load(f)

        proc = subprocess.Popen(self.cmd, stdout=subprocess.PIPE)
        stdout = ''

        while proc.poll() == None:
            current_time = int(time.time())
            if current_time - self.begin_time >= self.timeout:
                print('tcpdump timeout...')
                proc.send_signal(signal.SIGINT)
                stdout = proc.stdout.read()

        if proc.poll() is not None and not stdout:
            print('tcpdump finished...')
            stdout = proc.stdout.read()

        stdout_list = stdout.split('\n')
        if stdout_list:
            self._merge_data(stdout_list)
            self._save_data()

    # 数据合并(新增/更新)
    def _merge_data(self, stdout_list):
        for line in stdout_list:
            line = line.strip()
            if not line:
                continue
            timestamp = int(float(line.split('IP')[0].strip())) * 1000
            # 源
            src_ip_port_list = re.findall(r'IP(.+?)>', line)
            if not src_ip_port_list:
                continue
            src_ip_port_str = src_ip_port_list[0].strip()
            src_ip = '.'.join(src_ip_port_str.split('.')[0:4])
            # 目的
            dst_ip_port_list = re.findall(r'>(.+?):', line)
            if not dst_ip_port_list:
                continue
            dst_ip_port_str = dst_ip_port_list[0].strip()
            dst_port = dst_ip_port_str.split('.')[-1]

            # 新增/更新latest_timestamp
            src_item = filter(lambda x: src_ip == x['src_ip'], self.udp_flow_list)
            if src_item:
                src_item[0]['dst_port'] = dst_port
                src_item[0]['latest_timestamp'] = timestamp
            else:
                self.udp_flow_list.append(dict(
                    src_ip=src_ip,
                    dst_port=dst_port,
                    latest_timestamp=timestamp
                ))

    # 保存数据
    def _save_data(self):
        # 写入文件
        with open(self.output_path, 'w') as f:
            json.dump(self.udp_flow_list, f, encoding="utf-8", ensure_ascii=False)


if __name__ == '__main__':
    cmd = ['sudo', 'tcpdump', '-tt', '-l', '-nn', '-c', '5', '-i', 'enp4s0', 'udp', 'port', '514', 'or', '51414']
    cmd_server = CmdServer(cmd, 10)
    cmd_server.run()

四、总结

  比较简单,仅仅是记录下。如果想基于Python的tcpdump做一些业务上的逻辑,可以参考下面的“参考链接”。


参考
https://blog.csdn.net/wskzgz/article/details/83822780
https://blog.csdn.net/wangqiuyun/article/details/46966839
http://www.cnblogs.com/idvcn/p/8716066.html
https://blog.csdn.net/kobeyan/article/details/4344192
https://blog.csdn.net/xhw88398569/article/details/48022967


  目录