系统架构与设计
MVC架构
应用程序采用了模型-视图-控制器(MVC)架构,这是一种用于应用程序开发的常用设计模式,可以将应用程序的输入、处理和输出分离。这样做既可以提高代码的可维护性,也便于将来的扩展。
- 模型(Model): 在本应用中,模型层直接与Scapy交互,负责数据包的捕获和分析逻辑。
- 视图(View): 视图层由Tkinter构建,为用户提供交互界面。
- 控制器(Controller): 控制器连接模型和视图,处理用户输入并调用模型来反映数据变化。
关键类说明
- PacketSnifferController:作为控制器,负责启动嗅探线程和更新视图层的数据。
- WinGUI:构建并管理GUI组件,如按钮、表格、输入框等。
- Win:继承自WinGUI,实现事件绑定和自定义样式。
技术实现细节
使用Tkinter构建GUI
Tkinter提供了一套丰富的控件来构建图形用户界面。在本项目中,我们利用了Tkinter的Button
、Entry
、Combobox
、Scale
和Treeview
等控件来创建一个用户友好的界面。通过这些控件,用户可以轻松设置嗅探参数并启动嗅探过程。
多线程数据包嗅探
数据包嗅探是一个持续的过程,如果在主线程中执行,将阻塞GUI的响应。因此,我们采用threading
模块创建一个新线程来进行数据包嗅探。这样,即使在捕获数据包时,用户界面也能保持响应性。
Scapy进行数据包捕获
Scapy是一个强大的交互式数据包处理程序,支持广泛的协议。在我们的应用程序中,Scapy用于捕获和分析ARP数据包。Scapy的sniff
函数允许我们指定捕获的接口、数据包数量以及过滤规则,非常适合于构建灵活的网络嗅探器。
动态获取网络接口
通过psutil库,我们能够动态获取系统中的网络接口信息,并在GUI中以下拉列表的形式展示给用户。这增加了应用程序的灵活性,允许用户根据实际情况选择合适的网络接口进行嗅探。
应用程序使用
- 安装必要的库:
pip install scapy psutil tkinter
。 - 运行脚本启动应用程序。
- 选择网络接口,设置要捕获的数据包数量。
- 点击“开始嗅探”按钮,观察捕获的数据包信息。
附上源码
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()