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

Mac环境下如何阻止系统为Python发送的数据包自动添加IP头

Mac下Python发送自定义IP头原始数据包的解决方法

我尝试用Python发送带自定义IP头和ICMP负载的原始IP数据包,运行代码后Wireshark显示系统自动给数据包加了以太网头和IP头。我希望只让系统添加以太网头,IP头完全由自己定义。

原代码如下:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)

# IP header
ip_header = b'\x45\x00\x00\x1c'     # Version, IHL, Type of Service, Total Length
ip_header += b'\xab\xcd\x00\x00'    # Identification, Flags, Fragment Offset
ip_header += b'\x40\x01\x6b\xd8'    # TTL, Protocol, Header Checksum
ip_header += b'\xc0\xa8\x92\x83'    # Source Address
ip_header += b'\x08\x08\x08\x08'    # Destination Address

# IP payload/ICMP header
icmp_header = b'\x08\x00\xe5\xca'   # Type of message, Code, Checksum
icmp_header += b'\x12\x34\x00\x01'  # Identifier, Sequence Number

packet = ip_header + icmp_header

s.sendto(packet, ('8.8.8.8', 1))
s.close()

Wireshark捕获到的数据包情况:
Wireshark捕获的数据包

我原本以为设置socket.IP_HDRINCL就能让系统不再自动添加IP头,于是在创建socket后加了这段代码:

s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

但添加后,数据包完全不在Wireshark里显示了。我还试过把协议改成socket.IPPROTO_IP,系统还是会自动添加IP头。

我用的是Mac系统,通过sudo python3 code.py运行代码,想问下怎么让Python/操作系统不再自动给数据包添加IP头?


问题根源

Mac系统的raw socket行为和Linux存在差异:

  • 当用IPPROTO_RAW创建SOCK_RAW socket时,默认内核会自动生成IP头;即便开启IP_HDRINCL,如果自定义IP头的校验和不正确,内核会直接丢弃数据包,导致Wireshark捕获不到。
  • Mac内核不会自动修正自定义IP头的校验和,必须手动计算正确值才能让数据包正常发出。

解决步骤

  1. 开启IP_HDRINCL选项:明确告诉内核我们将自行提供完整的IP头。
  2. 手动计算IP头校验和:开启IP_HDRINCL后内核不再负责校验和计算,错误的校验和会导致数据包被丢弃。
  3. 确保数据包长度匹配:IP头中的总长度字段必须与实际数据包(IP头+负载)的字节数一致。

修正后的代码

import socket

def calculate_ip_checksum(ip_header):
    # 计算IP头校验和
    checksum = 0
    # 按16位分组遍历IP头
    for i in range(0, len(ip_header), 2):
        if i + 1 < len(ip_header):
            word = (ip_header[i] << 8) + ip_header[i+1]
            checksum += word
        else:
            checksum += ip_header[i] << 8
    # 合并高16位与低16位
    checksum = (checksum >> 16) + (checksum & 0xffff)
    # 取反得到最终校验和
    checksum = ~checksum & 0xffff
    return checksum.to_bytes(2, byteorder='big')

# 创建raw socket
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
# 开启IP_HDRINCL选项
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

# 构造IP头(先将校验和字段设为0)
ip_header = b'\x45\x00\x00\x1c'     # Version(4), IHL(5), Type of Service(0), Total Length(28)
ip_header += b'\xab\xcd\x00\x00'    # Identification, Flags, Fragment Offset
ip_header += b'\x40\x01\x00\x00'    # TTL(64), Protocol(ICMP=1), 校验和占位
ip_header += b'\xc0\xa8\x92\x83'    # 源IP地址
ip_header += b'\x08\x08\x08\x08'    # 目的IP地址

# 计算并替换校验和
ip_header = ip_header[:10] + calculate_ip_checksum(ip_header) + ip_header[12:]

# ICMP负载
icmp_header = b'\x08\x00\xe5\xca'   # ICMP类型(Echo Request), 代码(0), 校验和
icmp_header += b'\x12\x34\x00\x01'  # 标识符, 序列号

packet = ip_header + icmp_header

# 发送数据包,地址参数会被IP头中的目的地址覆盖,仅需传占位值
s.sendto(packet, ('0.0.0.0', 0))
s.close()

额外说明

  • 以太网头是内核自动添加的,属于正常行为,raw socket无法绕过数据链路层的以太网封装,这符合你“仅添加以太网头,自行提供IP头”的需求。
  • 必须用sudo运行代码,因为创建raw socket需要管理员权限。
  • 如果修改了ICMP负载,需要重新计算ICMP的校验和,否则数据包同样可能被丢弃。

内容的提问来源于stack exchange,提问作者Michael M.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 07:45:26