9.0 KiB
9.0 KiB
该代码是一个使用 Tkinter
库制作的网络通信软件,通过图形界面(GUI)允许用户发送不同类型的网络报文(如 ICMP、UDP、TCP)。以下是逐行解释代码:
导入必要的库
import tkinter as tk
from tkinter import ttk, messagebox
import socket
import struct
import time
import uuid
import re
tkinter
用于创建 GUI 界面。ttk
提供更丰富的部件,如下拉框(Combobox
)。messagebox
用于显示消息框。socket
用于创建网络连接。struct
用于处理二进制数据的打包和解包(比如构造报文头)。time
和uuid
用于获取当前时间戳和生成唯一的 MAC 地址。re
用于正则表达式操作,主要用于验证 IP 地址或域名的格式。
获取本机信息
def get_local_info():
hostname = socket.gethostname()
local_ips = socket.gethostbyname_ex(hostname)[-1]
mac = ':'.join(re.findall('..', '%012x' % uuid.getnode()))
return hostname, mac, local_ips
gethostname()
获取当前计算机的主机名。gethostbyname_ex(hostname)
返回主机名对应的 IP 地址列表,选择最后一个(所有可用的 IP 地址)。- 使用
uuid.getnode()
获取 MAC 地址,使用正则表达式将其格式化为XX:XX:XX:XX:XX:XX
的格式。
验证 IP 地址或域名
def validate_ip_or_domain(value):
ip_pattern = re.compile(r'^\d{1,3}(\.\d{1,3}){3}$')
domain_pattern = re.compile(r'^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$')
return ip_pattern.match(value) or domain_pattern.match(value)
- 使用正则表达式验证输入的值是否是有效的 IP 地址或域名。
验证端口号
def validate_port(port):
return port.isdigit() and 1 <= int(port) <= 65535
- 确保端口号是一个数字并在合法范围(1-65535)内。
构造 IP 头部
def construct_ip_header(src_ip, dest_ip, payload_length, protocol):
version = 4
ihl = 5
tos = 0
total_length = 20 + payload_length
identification = 54321
flags_offset = 0
ttl = 64
header_checksum = 0
src_ip_bytes = socket.inet_aton(src_ip)
dest_ip_bytes = socket.inet_aton(dest_ip)
ip_header = struct.pack('!BBHHHBBH4s4s',
(version << 4) + ihl, tos, total_length,
identification, flags_offset, ttl, protocol,
header_checksum, src_ip_bytes, dest_ip_bytes)
return ip_header
- 构造一个 IPv4 头部,使用
struct.pack()
将数据打包为二进制格式。 socket.inet_aton()
用于将 IP 地址转换为二进制格式。- 该函数生成一个标准的 IPv4 头部,包含源 IP、目的 IP 和协议类型等信息。
构造 ICMP 报文
def construct_icmp():
icmp_header = struct.pack('!BBHHH', 8, 0, 0, 1, 1)
data = b'Ping'
checksum = calculate_checksum(icmp_header + data)
icmp_header = struct.pack('!BBHHH', 8, 0, checksum, 1, 1)
return icmp_header + data
- ICMP 报文的类型为 8(Echo 请求)。
- 使用
struct.pack()
构造 ICMP 头部,并计算校验和。 - 校验和通过
calculate_checksum()
函数来计算,确保报文的完整性。
构造 UDP 报文
def construct_udp(src_port, dest_port):
udp_header = struct.pack('!HHHH', src_port, dest_port, 8, 0)
return udp_header
- 构造一个简单的 UDP 报文头,包含源端口、目标端口、长度和校验和。
构造 TCP 报文(SYN)
def construct_tcp(src_port, dest_port):
seq = 0
ack_seq = 0
offset = 5
reserved = 0
flags = 0b000010 # SYN flag
window = socket.htons(5840)
checksum = 0
urgent_ptr = 0
tcp_header = struct.pack('!HHLLBBHHH',
src_port, dest_port, seq, ack_seq,
(offset << 4) + reserved, flags, window,
checksum, urgent_ptr)
return tcp_header
- 构造 TCP 头部,设置 SYN 标志位以启动连接。
- 包含源端口、目标端口、序列号等信息。
计算校验和
def calculate_checksum(data):
checksum = 0
n = len(data)
for i in range(0, n - 1, 2):
chunk = (data[i] << 8) + data[i + 1]
checksum += chunk
if n % 2 == 1:
checksum += data[-1] << 8
checksum = (checksum >> 16) + (checksum & 0xFFFF)
checksum += (checksum >> 16)
return ~checksum & 0xFFFF
- 计算校验和,保证数据的完整性。
- 将数据按 2 字节分组求和,最终计算出一个 16 位的校验和。
发送报文
def send_packet():
try:
src_ip = ip_combobox.get()
dest_host = dest_entry.get()
dest_port = port_entry.get()
packet_type = var.get()
# 验证目标地址
if not validate_ip_or_domain(dest_host):
raise ValueError("目标地址无效,必须为有效的 IP 或域名")
# 验证端口号
if not validate_port(dest_port):
raise ValueError("端口号无效,必须是有效的正整数(1-65535)")
dest_ip = socket.gethostbyname(dest_host)
src_port = 12345
dest_port = int(dest_port)
# 构造报文并发送
if packet_type == "ICMP":
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
packet = construct_icmp()
elif packet_type == "UDP":
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
packet = construct_udp(src_port, dest_port)
elif packet_type == "TCP":
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((dest_ip, dest_port))
packet = b"Hello, TCP!" # TCP 是面向连接的,这里发送简单的字符串
sock.sendall(packet)
sock.close()
result.set(f"TCP 连接成功,数据已发送!")
return # 结束函数
# 发送报文
sock.sendto(packet, (dest_ip, dest_port))
result.set(f"报文发送成功!类型: {packet_type}")
except socket.gaierror:
messagebox.showerror("网络错误", "无法解析目标地址,请检查输入的 IP 或域名是否正确。")
except socket.error as e:
messagebox.showerror("网络错误", f"网络错误: {e.strerror}")
except ValueError as e:
messagebox.showerror("输入错误", f"输入无效: {e}")
except Exception as e:
messagebox.showerror("未知错误", f"发送报文时发生未知错误: {e}")
- 根据用户选择的目标地址、端口号和报文类型,构造并发送相应的网络报文。
- 对地址和端口进行验证,若无误则根据选择的报文类型(ICMP、UDP、TCP)构造报文并发送。
- 如果是 TCP 报文,使用
socket.SOCK_STREAM
进行连接并发送数据。 - 若发送成功,通过
messagebox.showerror()
显示错误信息。
GUI 界面设计
window = tk.Tk()
window.title("网络通信软件")
- 创建一个 Tkinter 窗口,设置标题为“网络通信软件”。
hostname, mac, local_ips = get_local_info()
- 获取本机的主机名、MAC 地址和可用的 IP 地址。
tk.Label(window, text="本机信息:").grid(row=0, column=0)
info_text = tk.Text(window, height=4, width=40)
info_text.grid(row=0, column=1, columnspan=2)
info_text.insert(tk.END, f"主机名: {hostname}\nMAC 地址: {mac}\n可用 IP: {', '.join(local_ips)}")
info_text.config(state=tk.DISABLED)
- 显示本机的相关信息,如主机名、MAC 地址和可用 IP 地址。
tk.Label(window, text="本机 IP 地址:").grid(row=1, column=0)
ip_combobox = ttk.Combobox(window, values=local_ips)
ip_combobox.grid(row=1, column=1)
ip_combobox.current(0)
- 提供一个下拉框,显示本机的 IP 地址供用户选择。
tk.Label(window, text="目标地址:").grid(row=2, column=0)
dest_entry = tk.Entry(window)
dest_entry.grid(row=2, column=1)
- 提供一个文本框供用户输入目标地址。
tk.Label(window, text="目标端口:").grid(row=3, column=0)
port_entry = tk.Entry(window)
port_entry.grid(row=3, column=1)
- 提供一个文本框供用户输入目标端口。
tk.Label(window, text="报文类型:").grid(row=4, column=0)
var = tk.StringVar(value="ICMP")
tk.Radiobutton(window, text="ICMP", variable=var, value="ICMP").grid(row=4, column=1)
tk.Radiobutton(window, text="UDP", variable=var, value="UDP").grid(row=4, column=2)
tk.Radiobutton(window, text="TCP", variable=var, value="TCP").grid(row=4, column=3)
- 提供单选按钮供用户选择报文类型(ICMP、UDP、TCP)。
result = tk.StringVar()
tk.Label(window, textvariable=result).grid(row=6, column=0, columnspan=3)
- 显示报文发送结果。
tk.Button(window, text="发送报文", command=send_packet).grid(row=5, column=1)
- 创建一个按钮,点击后调用
send_packet()
函数发送报文。
window.mainloop()
- 启动 GUI 窗口的主循环,使窗口保持显示状态。