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

使用Python原始套接字发送TCP SYN时setsockopt报错问题

解决Python原始套接字发送TCP SYN包的IP_HDRINCL报错问题

咱一步步拆解下你遇到的问题:设置IP_HDRINCL选项后触发[Errno 22] Invalid argument,去掉它虽然能运行但Wireshark抓到两个TCP头,核心原因是原始套接字中IP头的控制权分配问题。

为什么加IP_HDRINCL=1会报错?

当你通过setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)告诉内核“我自己来构造IP头”时,内核会严格校验你发送的数据包——如果你的代码没有构造完整且符合规范的IP头(比如IP版本不对、头长度计算错误、校验和未正确生成、总长度不匹配等),内核就会判定参数无效,抛出Errno 22。

为什么去掉选项后会出现两个TCP头?

不设置IP_HDRINCL时,内核会自动帮你填充IP头,但如果你的代码里已经构造了完整的TCP头,再加上某些系统下原始套接字的默认行为(比如内核误以为你需要额外的TCP头封装),就会出现重复的TCP头。

解决方案

方案1:保留IP_HDRINCL=1,正确构造完整IP+TCP头

如果你想手动控制整个IP包,就必须严格按照IP和TCP协议规范构造每一个字段,尤其是校验和(IP校验和、TCP校验和,TCP校验和还需要包含伪IP头)。这里给你一个简化的构造示例:

import socket
import struct

def calculate_checksum(data):
    s = 0
    # 按16位分组求和
    for i in range(0, len(data), 2):
        if i + 1 < len(data):
            w = (data[i] << 8) + data[i+1]
        else:
            w = data[i] << 8
        s += w
    # 折叠进位
    s = (s >> 16) + (s & 0xffff)
    s = ~s & 0xffff
    return s

source_ip = "192.168.1.100"
dest_ip = "192.168.1.200"
dest_port = 27015

# 构造IP头
ip_version_ihl = 0x45  # IPv4 + 5个32位字的头长度
ip_tos = 0
ip_total_len = 0  # 后面会动态计算
ip_id = 12345
ip_flags_frag = 0
ip_ttl = 64
ip_proto = socket.IPPROTO_TCP
ip_checksum = 0
ip_src = socket.inet_aton(source_ip)
ip_dst = socket.inet_aton(dest_ip)

ip_header = struct.pack(
    '!BBHHHBBH4s4s',
    ip_version_ihl, ip_tos, ip_total_len,
    ip_id, ip_flags_frag, ip_ttl, ip_proto,
    ip_checksum, ip_src, ip_dst
)

# 构造TCP SYN包的TCP头
tcp_src_port = 12345
tcp_dst_port = dest_port
tcp_seq = 1000
tcp_ack_seq = 0
tcp_data_off_res = 0x50  # 数据偏移(5个32位字)+ 保留位
tcp_flags = 0x02  # SYN标志位
tcp_window = socket.htons(65535)
tcp_checksum = 0
tcp_urg_ptr = 0

tcp_header = struct.pack(
    '!HHLLBBHHH',
    tcp_src_port, tcp_dst_port, tcp_seq, tcp_ack_seq,
    tcp_data_off_res, tcp_flags, tcp_window,
    tcp_checksum, tcp_urg_ptr
)

# 构造TCP伪IP头(用于计算TCP校验和)
pseudo_header = struct.pack(
    '!4s4sBBH',
    ip_src, ip_dst, 0, ip_proto, len(tcp_header)
)
tcp_checksum = calculate_checksum(pseudo_header + tcp_header)
tcp_header = tcp_header[:16] + struct.pack('!H', tcp_checksum) + tcp_header[18:]

# 计算IP总长度并更新IP头
ip_total_len = len(ip_header) + len(tcp_header)
ip_header = ip_header[:2] + struct.pack('!H', ip_total_len) + ip_header[4:]
# 计算IP校验和并更新IP头
ip_checksum = calculate_checksum(ip_header)
ip_header = ip_header[:10] + struct.pack('!H', ip_checksum) + ip_header[12:]

# 发送完整数据包
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
s.sendto(ip_header + tcp_header, (dest_ip, dest_port))

方案2:不设置IP_HDRINCL,仅构造TCP头

如果你不想手动处理IP头,就只构造TCP SYN的TCP头部分,让内核自动填充IP头。这时候要确保你的代码只发送TCP头,不要包含多余的内容,避免内核重复封装TCP头。示例片段:

import socket
import struct

def calculate_checksum(data):
    s = 0
    for i in range(0, len(data), 2):
        if i + 1 < len(data):
            w = (data[i] << 8) + data[i+1]
        else:
            w = data[i] << 8
        s += w
    s = (s >> 16) + (s & 0xffff)
    s = ~s & 0xffff
    return s

source_ip = "192.168.1.100"
dest_ip = "192.168.1.200"
dest_port = 27015

# 构造TCP SYN头
tcp_src_port = 12345
tcp_dst_port = dest_port
tcp_seq = 1000
tcp_ack_seq = 0
tcp_data_off_res = 0x50
tcp_flags = 0x02
tcp_window = socket.htons(65535)
tcp_checksum = 0
tcp_urg_ptr = 0

tcp_header = struct.pack(
    '!HHLLBBHHH',
    tcp_src_port, tcp_dst_port, tcp_seq, tcp_ack_seq,
    tcp_data_off_res, tcp_flags, tcp_window,
    tcp_checksum, tcp_urg_ptr
)

# 构造伪IP头计算TCP校验和
pseudo_header = struct.pack(
    '!4s4sBBH',
    socket.inet_aton(source_ip),
    socket.inet_aton(dest_ip),
    0, socket.IPPROTO_TCP, len(tcp_header)
)
tcp_checksum = calculate_checksum(pseudo_header + tcp_header)
tcp_header = tcp_header[:16] + struct.pack('!H', tcp_checksum) + tcp_header[18:]

# 发送TCP头(内核自动加IP头)
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
# 注释掉IP_HDRINCL设置
# s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
s.sendto(tcp_header, (dest_ip, dest_port))

额外注意事项

  • 运行原始套接字代码必须拥有root/管理员权限,否则会出现权限错误。
  • 不同操作系统(Linux、Windows、macOS)对原始套接字的处理逻辑有差异,上面的代码可能需要根据你的系统做微调。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:32:22