【源码】python网络ARP抓包工具

本文介绍了如何使用Python的MVC架构(模型-视图-控制器)结合Scapy和Tkinter开发一个数据包嗅探器。应用通过Scapy捕获网络数据包,使用Tkinter构建用户界面,实现多线程以保证GUI响应性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

系统架构与设计

MVC架构

应用程序采用了模型-视图-控制器(MVC)架构,这是一种用于应用程序开发的常用设计模式,可以将应用程序的输入、处理和输出分离。这样做既可以提高代码的可维护性,也便于将来的扩展。

  • 模型(Model): 在本应用中,模型层直接与Scapy交互,负责数据包的捕获和分析逻辑。
  • 视图(View): 视图层由Tkinter构建,为用户提供交互界面。
  • 控制器(Controller): 控制器连接模型和视图,处理用户输入并调用模型来反映数据变化。

关键类说明

  • PacketSnifferController:作为控制器,负责启动嗅探线程和更新视图层的数据。
  • WinGUI:构建并管理GUI组件,如按钮、表格、输入框等。
  • Win:继承自WinGUI,实现事件绑定和自定义样式。

技术实现细节

使用Tkinter构建GUI

Tkinter提供了一套丰富的控件来构建图形用户界面。在本项目中,我们利用了Tkinter的ButtonEntryComboboxScaleTreeview等控件来创建一个用户友好的界面。通过这些控件,用户可以轻松设置嗅探参数并启动嗅探过程。

多线程数据包嗅探

数据包嗅探是一个持续的过程,如果在主线程中执行,将阻塞GUI的响应。因此,我们采用threading模块创建一个新线程来进行数据包嗅探。这样,即使在捕获数据包时,用户界面也能保持响应性。

Scapy进行数据包捕获

Scapy是一个强大的交互式数据包处理程序,支持广泛的协议。在我们的应用程序中,Scapy用于捕获和分析ARP数据包。Scapy的sniff函数允许我们指定捕获的接口、数据包数量以及过滤规则,非常适合于构建灵活的网络嗅探器。

动态获取网络接口

通过psutil库,我们能够动态获取系统中的网络接口信息,并在GUI中以下拉列表的形式展示给用户。这增加了应用程序的灵活性,允许用户根据实际情况选择合适的网络接口进行嗅探。

应用程序使用

  1. 安装必要的库:pip install scapy psutil tkinter
  2. 运行脚本启动应用程序。
  3. 选择网络接口,设置要捕获的数据包数量。
  4. 点击“开始嗅探”按钮,观察捕获的数据包信息。

附上源码

import threading
import tkinter as tk
from tkinter.ttk import Treeview, Combobox, Button, Entry, Scale, Style
from scapy.all import sniff
from scapy.layers.l2 import Ether
from scapy.layers.l2 import ARP
import psutil
class PacketSnifferController:
    def __init__(self):
        self.win = None
        self.packet_count = 10  # 默认捕获数据包数量

    def init(self, win):
        self.win = win

    def start_sniffing(self):
        # 获取用户设置的数据包数量
        self.packet_count = self.win.tk_input_lrg2klel.get()

        # 启动数据包嗅探线程
        sniff_thread = threading.Thread(target=self.sniff_packets)
        sniff_thread.start()

    def sniff_packets(self):
        selected_interface = self.win.tk_select_box_lrg2i35c.get()
        print(self.packet_count,selected_interface)
        # 设置捕获条件,可以根据需要进行修改
        # 在这个例子中,只捕获前 self.packet_count 个 ARP 数据包
        packets = sniff(iface=selected_interface, count=int(self.packet_count), filter="arp", stop_filter=None)

        # 处理捕获到的数据包
        for packet in packets:
            self.packet_callback(packet)

    def packet_callback(self, packet):
        # 获取捕获的数据包信息
        captured_info = (
            packet[Ether].dst,
            packet[Ether].src,
            packet[Ether].type,
            packet[ARP].hwtype,
            packet[ARP].ptype,
            packet[ARP].hwlen,
            packet[ARP].plen,
            packet[ARP].op,
            packet[ARP].hwsrc,
            packet[ARP].psrc,
            packet[ARP].hwdst,
            packet[ARP].pdst
        )

        print("Captured info before insertion:", captured_info)  # 调试输出

        # 插入新行数据,从第一列开始
        self.win.tk_table_lrg2fz4k.insert("", tk.END, values=captured_info)

class WinGUI(tk.Tk):
    def __init__(self):
        super().__init__()
        self.__win()
        self.tk_button_lrg2fjic = self.__tk_button_lrg2fjic(self)
        self.tk_table_lrg2fz4k = self.__tk_table_lrg2fz4k(self)
        self.tk_label_lrg2h7am = self.__tk_label_lrg2h7am(self)
        self.tk_select_box_lrg2i35c = self.__tk_select_box_lrg2i35c(self)
        self.tk_frame_lrg2jofg = self.__tk_frame_lrg2jofg(self)
        self.tk_input_lrg2klel = self.__tk_input_lrg2klel(self.tk_frame_lrg2jofg)
        self.tk_scale_lrg2krvw = self.__tk_scale_lrg2krvw(self.tk_frame_lrg2jofg)
        self.tk_label_lrg2l0vn = self.__tk_label_lrg2l0vn(self.tk_frame_lrg2jofg)

    def __win(self):
        self.title("数据包嗅探器")
        # 设置窗口大小、居中
        width = 1600
        height = 500
        screenwidth = self.winfo_screenwidth()
        screenheight = self.winfo_screenheight()
        geometry = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
        self.geometry(geometry)

        self.resizable(width=False, height=False)

    def scrollbar_autohide(self, vbar, hbar, widget):
        """自动隐藏滚动条"""
        def show():
            if vbar: vbar.lift(widget)
            if hbar: hbar.lift(widget)
        def hide():
            if vbar: vbar.lower(widget)
            if hbar: hbar.lower(widget)
        hide()
        widget.bind("<Enter>", lambda e: show())
        if vbar: vbar.bind("<Enter>", lambda e: show())
        if vbar: vbar.bind("<Leave>", lambda e: hide())
        if hbar: hbar.bind("<Enter>", lambda e: show())
        if hbar: hbar.bind("<Leave>", lambda e: hide())
        widget.bind("<Leave>", lambda e: hide())

    def v_scrollbar(self, vbar, widget, x, y, w, h, pw, ph):
        widget.configure(yscrollcommand=vbar.set)
        vbar.config(command=widget.yview)
        vbar.place(relx=(w + x) / pw, rely=y / ph, relheight=h / ph, anchor='ne')

    def h_scrollbar(self, hbar, widget, x, y, w, h, pw, ph):
        widget.configure(xscrollcommand=hbar.set)
        hbar.config(command=widget.xview)
        hbar.place(relx=x / pw, rely=(y + h) / ph, relwidth=w / pw, anchor='sw')

    def create_bar(self, master, widget, is_vbar, is_hbar, x, y, w, h, pw, ph):
        vbar, hbar = None, None
        if is_vbar:
            vbar = tk.Scrollbar(master)
            self.v_scrollbar(vbar, widget, x, y, w, h, pw, ph)
        if is_hbar:
            hbar = tk.Scrollbar(master, orient="horizontal")
            self.h_scrollbar(hbar, widget, x, y, w, h, pw, ph)
        self.scrollbar_autohide(vbar, hbar, widget)

    def __tk_button_lrg2fjic(self, parent):
        btn = Button(parent, text="开始嗅探", takefocus=False)
        btn.place(x=520, y=71, width=67, height=30)
        return btn

    def __tk_table_lrg2fz4k(self, parent):
        # 表头字段 表头宽度
        columns = {"目的地址": 119, "源地址": 179, "类型": 100, "硬件类型": 100, "协议类型": 100, "硬件地址长度": 130,
                   "协议地址长度": 130, "操作码": 100, "源硬件地址": 150, "源协议地址": 150, "目的硬件地址": 150,
                   "目的协议地址": 150}
        tk_table = Treeview(parent, show="headings", columns=list(columns))
        for text, width in columns.items():  # 批量设置列属性
            tk_table.heading(text, text=text, anchor='center')
            tk_table.column(text, anchor='center', width=width, stretch=False)  # stretch 不自动拉伸

        tk_table.place(x=0, y=166, width=1600, height=334)
        return tk_table

    def __tk_label_lrg2h7am(self, parent):
        label = tk.Label(parent, text="选择网络接口:", anchor="center")
        label.place(x=11, y=14, width=112, height=30)
        return label

    def __tk_select_box_lrg2i35c(self, parent):
        cb = Combobox(parent, state="readonly", values=list(psutil.net_if_stats().keys()))
        cb.place(x=138, y=16, width=150, height=30)
        return cb

    def __tk_frame_lrg2jofg(self, parent):
        frame = tk.Frame(parent)
        frame.place(x=312, y=2, width=200, height=150)
        return frame

    def __tk_input_lrg2klel(self, parent):
        ipt = Entry(parent)
        ipt.insert(0, "10")  # 设置默认值为 "10"
        ipt.place(x=153, y=100, width=41, height=30)
        return ipt

    def __tk_scale_lrg2krvw(self, parent):
        scale = Scale(parent, orient=tk.HORIZONTAL, from_=1, to=100, command=self.update_entry_value)
        scale.place(x=11, y=98, width=130, height=30)
        return scale

    def update_entry_value(self, value):
        try:
            int_value = int(float(value))
            self.tk_input_lrg2klel.delete(0, tk.END)
            self.tk_input_lrg2klel.insert(0, str(int_value))
        except ValueError:
            # 处理无效值的情况,可以在这里添加适当的错误处理或默认值
            pass

    def __tk_label_lrg2l0vn(self, parent):
        label = tk.Label(parent, text="捕获数据包数量", anchor="center")
        label.place(x=10, y=28, width=180, height=30)
        return label

class Win(WinGUI):
    def __init__(self, controller):
        self.ctl = controller
        super().__init__()
        self.__event_bind()
        self.__style_config()
        self.ctl.init(self)

    def __event_bind(self):
        self.tk_button_lrg2fjic.config(command=self.ctl.start_sniffing)

    def __style_config(self):
        # 设置 Treeview 样式
        style = Style()
        style.configure("Treeview", rowheight=30)
        style.configure("Treeview.Heading", font=("Arial", 10, "bold"))

if __name__ == "__main__":
    controller = PacketSnifferController()
    win = Win(controller)
    win.mainloop()

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农之家★资源共享

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值