Python-web-communicate/代码解释.md
fly6516 a0d3d8363f docs(代码解释): 更新网络通信软件的代码解释文档
-优化了代码解释的结构和内容,使其更加清晰和详细
- 增加了对 DNS 查询报文构造的解释
-改进了对 IP 头部和 TCP 报文的解释
- 调整了部分术语和描述,使其更符合技术文档的标准
2025-01-08 03:32:26 +08:00

8.5 KiB
Raw Blame History

这段代码实现了一个基本的网络通信工具允许用户通过图形用户界面GUI构造并发送不同类型的网络报文如ICMP、UDP、TCP、DNS、IP。让我们逐部分深入分析代码的实现原理和工作方式。

1. 导入模块

import tkinter as tk
from tkinter import ttk, messagebox
import socket
import struct
import time
import uuid
import re
import random
  • tkinter: 用于构建GUI提供创建窗口、标签、按钮等界面元素的功能。
  • socket: 用于低层次的网络操作,如创建套接字、发送和接收数据等。
  • struct: 用于处理二进制数据的打包和解包,例如网络数据报文的处理。
  • time: 用于获取时间戳、计算延迟等。
  • uuid: 用于获取唯一的标识符特别是获取本机的MAC地址。
  • re: 用于正则表达式主要用来验证输入的IP地址或域名的格式。
  • random: 用于生成随机数这里主要用在构造DNS查询报文时随机生成事务ID。

2. 获取本机信息

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
  • socket.gethostname(): 获取当前计算机的主机名。
  • socket.gethostbyname_ex(hostname)[-1]: 根据主机名获取本机所有的IP地址IPv4返回的列表中的最后一个元素包含了所有本机的IP。
  • uuid.getnode(): 获取计算机的硬件地址MAC地址。返回的是一个64位的数字我们通过格式化成16进制并使用正则表达式将其格式化为常见的MAC地址格式00:1A:2B:3C:4D:5E)。

3. 验证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地址或域名的格式。
    • IP地址格式: \d{1,3}(\.\d{1,3}){3} 匹配四个以点分隔的1到3位数字。
    • 域名格式: ([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,} 匹配由字母、数字或中划线组成的域名部分和一个或多个子域名部分。

4. 验证端口号

def validate_port(port):
    return port.isdigit() and 1 <= int(port) <= 65535
  • 该函数检查端口号是否在1到65535的范围内确保用户输入的是有效的端口号。

5. 构造IP头部

def construct_ip_header(src_ip, dest_ip, payload_length, protocol):
    version = 4
    ihl = 5
    tos = 0
    total_length = 20 + payload_length  # 20字节的IP头部 + 数据部分长度
    identification = 54321
    flags_offset = 0
    ttl = 64
    header_checksum = 0

    src_ip_bytes = socket.inet_aton(src_ip)  # 转换IP地址为二进制格式
    dest_ip_bytes = socket.inet_aton(dest_ip)  # 转换目标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
  • 该函数用于构造IP头部按网络字节序大端字节序格式将各种字段打包。
    • struct.pack: 用于将数据按指定格式打包成二进制数据。格式'!BBHHHBBH4s4s'表示:
      • !:大端字节序。
      • B一个字节8位表示一个无符号整数。
      • H两个字节16位表示一个无符号整数。
      • 4s4个字节的字符串即4个字符的IP地址
    • 字段说明:
      • 版本version: 使用IPv4协议版本号为4。
      • 头部长度ihl: 5表示头部长度为5个32位字即20字节
      • 总长度total_length: 包括IP头部和数据部分的总长度。
      • TTLttl: 存活时间Time To Live默认为64。
      • 协议protocol: 用于指示数据部分使用的协议类型如TCP、UDP、ICMP等。

6. 构造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报文用于网络诊断例如Ping
    • **struct.pack('!BBHHH', 8, 0, 0, 1, 1)**构造一个ICMP头部。8表示回显请求类型,0表示无错误,checksumseq等字段在后续计算和填充。
    • calculate_checksum: 计算ICMP报文的校验和确保数据在传输过程中没有被篡改。
    • 数据部分: 这里简单地将“Ping”作为数据发送实际应用中可以是更复杂的内容。

7. 构造UDP报文

def construct_udp(src_port, dest_port):
    udp_header = struct.pack('!HHHH', src_port, dest_port, 8, 0)
    return udp_header
  • 构造UDP头部包含源端口、目标端口、长度和校验和。这里暂时将校验和置为0。

8. 构造TCP报文SYN

def construct_tcp(src_port, dest_port):
    seq = 0
    ack_seq = 0
    offset = 5
    reserved = 0
    flags = 0b000010  # SYN标志位为1表示建立连接
    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
  • SYN报文用于TCP的三次握手建立连接时发送。
    • **flags = 0b000010**表示SYN标志为1。
    • window: 接收窗口大小。
    • seqack_seq: TCP的序列号和确认号初始化为0。

9. 计算校验和

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
  • 该函数计算数据的校验和,主要用于检测数据在传输过程中是否发生了错误。
    • 将数据拆分为16位块并求和。
    • 如果数据长度为奇数,则补零。
    • 最后对结果取反并返回16位校验和。

10. 构造DNS查询报文

def build_dns_query(domain):
   

 # 随机生成事务ID
    txid = random.randint(0, 65535)
    flags = 0x0100  # 查询标志
    qdcount = 1  # 查询数量
    ancount = 0  # 回答数量
    nscount = 0  # 权威服务器数量
    arcount = 0  # 附加记录数量

    query = struct.pack('>HHHHHH', txid, flags, qdcount, ancount, nscount, arcount)
    query += encode_domain(domain)
    query += struct.pack('>HH', 1, 1)  # 类型为A记录IPv4地址

    return query
  • **build_dns_query**构造DNS查询报文查询给定域名的A记录即返回IPv4地址
    • encode_domain(domain): 将域名按照DNS协议格式编码域名分段每段前面加上该段的长度。
    • 事务IDtxid随机生成查询的标志位设置为标准查询。

11. 发送报文

def send_packet():
    src_ip = ip_combobox.get()
    dest_host = dest_entry.get()
    dest_port = port_entry.get()
    packet_type = var.get()
  • 该函数会根据用户在GUI中输入的源IP地址、目标地址、目标端口和报文类型ICMP、UDP、TCP、DNS、IP来发送报文。具体的报文构造和发送过程会根据不同类型的报文选择相应的构造函数。

12. GUI界面设计与事件循环

window = tk.Tk()
window.title("网络通信软件")
  • 使用tkinter创建GUI窗口设置窗口标题和布局。
  • 通过**LabelEntryButton**等控件让用户输入本机IP、目标地址、端口和报文类型。
  • 通过**Button**控件触发发送报文的功能,调用 send_packet()

总结: 这段代码的核心功能是通过GUI界面实现用户自定义网络报文的构造、发送和反馈。主要操作包括获取本机信息、验证输入数据、根据不同报文类型如ICMP、TCP、UDP、DNS构造报文最后通过socket库将其发送到指定目标。