You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python struct.error报错解析、struct.unpack功能及ICMP请求实现问询

Hey there, let's break down your questions one by one!

1. 解析 struct.error: bad char in struct format 错误

这个错误本质是你传给struct.pack()或struct.unpack()的格式字符串里包含了不被struct模块支持的字符,或者字符组合不符合规则。常见触发场景有这些:

  • 格式字符串出现struct定义外的字符:比如误写"!XH",其中X不是合法格式符(合法的如H无符号短整型、I无符号整型、s字符串等)。
  • 字节序标记(!/</>/=)后跟无效字符:比如"!123H"写法错误,数量应放在格式符前,正确写法是"!2H"表示2个无符号短整型。
  • 格式符大小写误用:虽然大部分格式符大小写区分有符号/无符号,但使用完全不存在的大小写变体也会报错。

排查时直接拆分格式字符串,对照struct模块的格式符列表逐一检查,就能快速定位问题。

2. 解析 struct.unpack("!%sH" % (len(data) / 2), data) 的功能

这条语句的作用可以拆成三个清晰部分:

  • !:指定按照**网络字节序(大端模式)**解包,这是网络通信通用的字节序标准,保证跨平台兼容性。
  • "%sH" % (len(data)/2):动态生成格式字符串——比如data长度为4字节时,会变成"2H",表示要解出2个无符号短整型(每个H占2字节)。
  • 整体调用:将二进制数据data按上述规则解包,返回包含N个无符号短整型的元组,其中N等于len(data)/2。

⚠️ 重要注意:该语句要求len(data)必须是偶数,否则会触发另一个struct错误(每个H需要2字节空间)。举个例子,如果data是b'\x00\x01\x00\x02',解包后会得到(1, 2)。

3. 完成ICMP请求消息的Python实现

基于你给出的代码片段,我们需要补全create_packet()和nonprintable_to_dots()函数,同时完善接收逻辑。以下是符合ICMP协议标准的完整可运行实现:

import socket
import os
import struct
import sys
from functools import reduce

def nonprintable_to_dots():
    # 创建字节翻译表:将不可打印ASCII字符转为'.',可打印字符保留原样
    return bytes([ord('.') if i < 32 or i > 126 else i for i in range(256)])

def calculate_checksum(data):
    # 遵循RFC 1071标准计算ICMP包校验和
    if len(data) % 2 != 0:
        data += b'\x00'  # 数据长度为奇数时补空字节,保证按16位块处理
    # 将数据拆分为16位无符号整数
    words = struct.unpack('!%dH' % (len(data)//2), data)
    # 累加所有16位块
    checksum = reduce(lambda a, b: a + b, words, 0)
    # 处理溢出:将高16位与低16位相加,直到无溢出
    checksum = (checksum >> 16) + (checksum & 0xffff)
    checksum += checksum >> 16
    # 返回按位取反的16位结果(网络字节序)
    return ~checksum & 0xffff

def create_packet(code):
    # ICMP回显请求的类型固定为8,代码使用传入参数
    icmp_type = 8
    icmp_code = code
    checksum = 0  # 先填0,后续计算真实校验和
    # 用进程ID低16位作为标识符,方便匹配对应响应
    identifier = os.getpid() & 0xffff
    sequence = 1  # 序列号可自定义,这里固定为1
    
    # 构造不带校验和的ICMP头部
    header = struct.pack('!BBHHH', icmp_type, icmp_code, checksum, identifier, sequence)
    # 自定义数据部分,可根据需求修改
    data = b'Hello from ICMP Request!'
    
    # 计算整个ICMP包(头部+数据)的校验和
    checksum = calculate_checksum(header + data)
    # 重新构造带正确校验和的头部
    header = struct.pack('!BBHHH', icmp_type, icmp_code, checksum, identifier, sequence)
    
    # 返回完整ICMP包
    return header + data

def step4(code, server, port):
    try:
        # 创建原始ICMP套接字,需要管理员/root权限
        s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname('icmp'))
    except PermissionError:
        print("Error: 需要管理员权限才能创建原始套接字,请使用sudo运行此脚本")
        sys.exit(1)
    
    CHARMAP = nonprintable_to_dots()
    packet = create_packet(int(code))
    print(f"发送ICMP回显请求到 {server}...")
    # ICMP无需端口,port填0即可
    s.sendto(packet, (server, port))
    
    while True:
        # 接收响应,缓冲区1600字节足够容纳IP+ICMP包
        msg, addr = s.recvfrom(1600)
        # IP头部固定20字节(无选项),后续为ICMP包
        ip_header = msg[:20]
        icmp_packet = msg[20:]
        
        # 解析IP头部获取源IP
        ip_src = socket.inet_ntoa(ip_header[12:16])
        # 解析ICMP头部前8字节
        icmp_type, icmp_code, _, identifier, sequence = struct.unpack('!BBHHH', icmp_packet[:8])
        
        # 匹配我们发送的请求:ICMP类型为0(回显响应)且标识符一致
        if icmp_type == 0 and identifier == (os.getpid() & 0xffff):
            print(f"\n收到来自 {ip_src} 的ICMP回显响应")
            # 处理响应数据,转换不可打印字符并解码
            response_data = icmp_packet[8:].translate(CHARMAP).decode('ascii')
            print(f"响应数据内容: {response_data}")
            break
    s.close()

# 测试调用示例:发送请求到谷歌公共DNS,code设为0
if __name__ == "__main__":
    step4(0, '8.8.8.8', 0)

关键细节说明:

  • nonprintable_to_dots():生成翻译表,避免响应数据中的不可打印字符出现乱码,方便查看。
  • calculate_checksum():ICMP协议要求必须校验和,该函数严格遵循RFC标准,确保目标主机能正确识别请求。
  • create_packet():构造标准ICMP回显请求包,包含头部和自定义数据部分。
  • step4():补全接收与解析逻辑,通过进程ID匹配响应,确保只处理对应自身发送请求的回复。

内容的提问来源于stack exchange,提问作者thmasker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:04:42