2024-12-08 15:15:32 +00:00
|
|
|
|
import tkinter as tk
|
2025-01-06 06:44:58 +00:00
|
|
|
|
from tkinter import ttk, messagebox
|
2024-12-08 15:15:32 +00:00
|
|
|
|
import socket
|
|
|
|
|
import struct
|
|
|
|
|
import time
|
2025-01-06 06:44:58 +00:00
|
|
|
|
import uuid
|
|
|
|
|
import re
|
2024-12-08 15:15:32 +00:00
|
|
|
|
|
2025-01-06 06:44:58 +00:00
|
|
|
|
# 获取本机信息
|
|
|
|
|
def get_local_info():
|
2025-01-06 06:49:56 +00:00
|
|
|
|
"""
|
|
|
|
|
获取本机的主机名、MAC地址和IP地址列表。
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
tuple: 包含主机名、MAC地址和IP地址列表的元组。
|
|
|
|
|
"""
|
2025-01-06 06:44:58 +00:00
|
|
|
|
hostname = socket.gethostname()
|
|
|
|
|
local_ips = socket.gethostbyname_ex(hostname)[-1]
|
|
|
|
|
mac = ':'.join(re.findall('..', '%012x' % uuid.getnode()))
|
|
|
|
|
return hostname, mac, local_ips
|
|
|
|
|
|
|
|
|
|
# 验证 IP 地址或域名
|
|
|
|
|
def validate_ip_or_domain(value):
|
2025-01-06 06:49:56 +00:00
|
|
|
|
"""
|
|
|
|
|
验证输入值是否为有效的IP地址或域名。
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
value (str): 待验证的IP地址或域名。
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: 如果输入值有效返回True,否则返回False。
|
|
|
|
|
"""
|
2025-01-06 06:44:58 +00:00
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
# 验证端口号
|
|
|
|
|
def validate_port(port):
|
2025-01-06 06:49:56 +00:00
|
|
|
|
"""
|
|
|
|
|
验证输入的端口号是否有效。
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
port (str): 待验证的端口号。
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: 如果端口号有效返回True,否则返回False。
|
|
|
|
|
"""
|
2025-01-06 06:44:58 +00:00
|
|
|
|
return port.isdigit() and 1 <= int(port) <= 65535
|
|
|
|
|
|
|
|
|
|
# 构造 IP 头部
|
|
|
|
|
def construct_ip_header(src_ip, dest_ip, payload_length, protocol):
|
2025-01-06 06:49:56 +00:00
|
|
|
|
"""
|
|
|
|
|
构造IP头部。
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
src_ip (str): 源IP地址。
|
|
|
|
|
dest_ip (str): 目标IP地址。
|
|
|
|
|
payload_length (int): 负载长度。
|
|
|
|
|
protocol (int): 协议号。
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bytes: 构造的IP头部。
|
|
|
|
|
"""
|
2025-01-06 06:44:58 +00:00
|
|
|
|
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
|
2025-01-06 06:28:59 +00:00
|
|
|
|
|
2024-12-08 15:15:32 +00:00
|
|
|
|
# 构造 ICMP 报文
|
|
|
|
|
def construct_icmp():
|
2025-01-06 06:49:56 +00:00
|
|
|
|
"""
|
|
|
|
|
构造ICMP报文。
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bytes: 构造的ICMP报文。
|
|
|
|
|
"""
|
2024-12-08 15:15:32 +00:00
|
|
|
|
icmp_header = struct.pack('!BBHHH', 8, 0, 0, 1, 1)
|
2025-01-06 06:28:59 +00:00
|
|
|
|
data = b'Ping'
|
|
|
|
|
checksum = calculate_checksum(icmp_header + data)
|
2024-12-08 15:15:32 +00:00
|
|
|
|
icmp_header = struct.pack('!BBHHH', 8, 0, checksum, 1, 1)
|
2025-01-06 06:28:59 +00:00
|
|
|
|
return icmp_header + data
|
2024-12-08 15:15:32 +00:00
|
|
|
|
|
|
|
|
|
# 构造 UDP 报文
|
|
|
|
|
def construct_udp(src_port, dest_port):
|
2025-01-06 06:49:56 +00:00
|
|
|
|
"""
|
|
|
|
|
构造UDP报文。
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
src_port (int): 源端口号。
|
|
|
|
|
dest_port (int): 目标端口号。
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bytes: 构造的UDP报文。
|
|
|
|
|
"""
|
2024-12-08 15:15:32 +00:00
|
|
|
|
udp_header = struct.pack('!HHHH', src_port, dest_port, 8, 0)
|
|
|
|
|
return udp_header
|
|
|
|
|
|
2025-01-06 06:44:58 +00:00
|
|
|
|
# 构造 TCP 报文(SYN)
|
|
|
|
|
def construct_tcp(src_port, dest_port):
|
2025-01-06 06:49:56 +00:00
|
|
|
|
"""
|
|
|
|
|
构造TCP报文(SYN)。
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
src_port (int): 源端口号。
|
|
|
|
|
dest_port (int): 目标端口号。
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bytes: 构造的TCP报文。
|
|
|
|
|
"""
|
2025-01-06 06:44:58 +00:00
|
|
|
|
seq = 0
|
|
|
|
|
ack_seq = 0
|
|
|
|
|
offset = 5
|
|
|
|
|
reserved = 0
|
|
|
|
|
flags = 0b000010 # SYN flag
|
|
|
|
|
window = socket.htons(5840)
|
|
|
|
|
checksum = 0
|
|
|
|
|
urgent_ptr = 0
|
2024-12-08 15:15:32 +00:00
|
|
|
|
|
2025-01-06 06:44:58 +00:00
|
|
|
|
tcp_header = struct.pack('!HHLLBBHHH',
|
|
|
|
|
src_port, dest_port, seq, ack_seq,
|
|
|
|
|
(offset << 4) + reserved, flags, window,
|
|
|
|
|
checksum, urgent_ptr)
|
|
|
|
|
return tcp_header
|
2024-12-08 15:15:32 +00:00
|
|
|
|
|
2025-01-06 06:44:58 +00:00
|
|
|
|
# 计算校验和
|
|
|
|
|
def calculate_checksum(data):
|
2025-01-06 06:49:56 +00:00
|
|
|
|
"""
|
|
|
|
|
计算校验和。
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
data (bytes): 待计算校验和的数据。
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
int: 计算得到的校验和。
|
|
|
|
|
"""
|
2025-01-06 06:44:58 +00:00
|
|
|
|
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
|
2024-12-08 15:15:32 +00:00
|
|
|
|
|
|
|
|
|
# 发送报文
|
|
|
|
|
def send_packet():
|
2025-01-06 06:49:56 +00:00
|
|
|
|
"""
|
|
|
|
|
发送报文。
|
|
|
|
|
"""
|
2024-12-08 15:15:32 +00:00
|
|
|
|
try:
|
2025-01-06 06:44:58 +00:00
|
|
|
|
src_ip = ip_combobox.get()
|
|
|
|
|
dest_host = dest_entry.get()
|
|
|
|
|
dest_port = port_entry.get()
|
2024-12-08 15:15:32 +00:00
|
|
|
|
packet_type = var.get()
|
|
|
|
|
|
2025-01-06 06:44:58 +00:00
|
|
|
|
# 验证目标地址
|
|
|
|
|
if not validate_ip_or_domain(dest_host):
|
|
|
|
|
raise ValueError("目标地址无效,必须为有效的 IP 或域名")
|
|
|
|
|
|
|
|
|
|
# 验证端口号
|
|
|
|
|
if not validate_port(dest_port):
|
|
|
|
|
raise ValueError("端口号无效,必须是有效的正整数(1-65535)")
|
2024-12-08 15:15:32 +00:00
|
|
|
|
|
2025-01-06 06:44:58 +00:00
|
|
|
|
dest_ip = socket.gethostbyname(dest_host)
|
|
|
|
|
src_port = 12345
|
|
|
|
|
dest_port = int(dest_port)
|
|
|
|
|
|
|
|
|
|
# 构造报文并发送
|
2024-12-08 15:15:32 +00:00
|
|
|
|
if packet_type == "ICMP":
|
2025-01-06 06:44:58 +00:00
|
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
|
2024-12-08 15:15:32 +00:00
|
|
|
|
packet = construct_icmp()
|
|
|
|
|
elif packet_type == "UDP":
|
2025-01-06 06:44:58 +00:00
|
|
|
|
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}")
|
2024-12-08 15:15:32 +00:00
|
|
|
|
except Exception as e:
|
2025-01-06 06:44:58 +00:00
|
|
|
|
messagebox.showerror("未知错误", f"发送报文时发生未知错误: {e}")
|
2024-12-08 15:15:32 +00:00
|
|
|
|
|
|
|
|
|
# GUI 界面设计
|
|
|
|
|
window = tk.Tk()
|
|
|
|
|
window.title("网络通信软件")
|
|
|
|
|
|
2025-01-06 06:44:58 +00:00
|
|
|
|
hostname, mac, local_ips = get_local_info()
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
tk.Label(window, text="目标地址:").grid(row=2, column=0)
|
|
|
|
|
dest_entry = tk.Entry(window)
|
|
|
|
|
dest_entry.grid(row=2, column=1)
|
2024-12-08 15:15:32 +00:00
|
|
|
|
|
2025-01-06 06:44:58 +00:00
|
|
|
|
tk.Label(window, text="目标端口:").grid(row=3, column=0)
|
2024-12-08 15:15:32 +00:00
|
|
|
|
port_entry = tk.Entry(window)
|
2025-01-06 06:44:58 +00:00
|
|
|
|
port_entry.grid(row=3, column=1)
|
2024-12-08 15:15:32 +00:00
|
|
|
|
|
2025-01-06 06:44:58 +00:00
|
|
|
|
tk.Label(window, text="报文类型:").grid(row=4, column=0)
|
2024-12-08 15:15:32 +00:00
|
|
|
|
var = tk.StringVar(value="ICMP")
|
2025-01-06 06:44:58 +00:00
|
|
|
|
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)
|
2024-12-08 15:15:32 +00:00
|
|
|
|
|
|
|
|
|
result = tk.StringVar()
|
2025-01-06 06:44:58 +00:00
|
|
|
|
tk.Label(window, textvariable=result).grid(row=6, column=0, columnspan=3)
|
2024-12-08 15:15:32 +00:00
|
|
|
|
|
2025-01-06 06:44:58 +00:00
|
|
|
|
tk.Button(window, text="发送报文", command=send_packet).grid(row=5, column=1)
|
2024-12-08 15:15:32 +00:00
|
|
|
|
window.mainloop()
|