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

Python初学者咨询:用RAW Socket与struct.unpack解析HTTP头的格式字符串

用Raw Socket提取HTTP头的实操指南(结合struct.unpack)

嘿,作为Python初学者想搞Raw Socket抓HTTP头,这个思路挺有意思的!咱们一步步来理清楚:HTTP是基于TCP传输的,TCP又封装在IP包里,所以得先把IP头部和TCP头部用struct.unpack()解析出来,跳过这些头部后才能拿到TCP负载里的HTTP数据——毕竟HTTP头是文本格式,struct是用来处理IP/TCP那些二进制结构的,不能直接用它解HTTP头哦。

1. 先解析IPv4头部

IPv4头的基础长度是20字节(不带选项的情况),对应的struct格式字符串是!BBHHHBBH4s4s,每个字符的含义我给你拆解下:

  • !:代表网络字节序(大端序),网络传输的数据都是用大端序,必须加上这个保证解析正确
  • B:1字节的「版本+头部长度」(前4位是IP版本,后4位是头部长度,单位是4字节)
  • B:1字节的服务类型(一般不用管)
  • H:2字节的IP包总长度
  • H:2字节的标识(用于分片重组)
  • H:2字节的标志+片偏移(同样和分片有关)
  • B:1字节的TTL(生存时间)
  • B:1字节的协议号(TCP是6,UDP是17,我们只关心TCP)
  • H:2字节的头部校验和(验证头完整性)
  • 4s:4字节的源IP(二进制格式,需要用socket.inet_ntoa()转成字符串IP)
  • 4s:4字节的目的IP(同上)

给你一段示例代码:

import struct
import socket

# 假设raw_socket接收到的数据包存在packet变量里
ip_header = packet[:20]
ip_fields = struct.unpack('!BBHHHBBH4s4s', ip_header)

# 解析IP版本和头部实际长度
version_ihl = ip_fields[0]
ip_version = version_ihl >> 4  # 右移4位取版本(IPv4就是4)
ihl = version_ihl & 0xF  # 取后4位,乘以4得到IP头总长度(处理带选项的IP包)
protocol = ip_fields[6]  # 判断是不是TCP包(值为6)
src_ip = socket.inet_ntoa(ip_fields[8])
dst_ip = socket.inet_ntoa(ip_fields[9])

2. 再解析TCP头部

TCP头的最小长度是20字节(不带选项),对应的格式字符串是!HHLLBBHHH,含义如下:

  • !:还是网络字节序
  • H:2字节的源端口
  • H:2字节的目的端口(HTTP常用80,HTTPS是443,但HTTPS加密没法直接解析)
  • L:4字节的序列号
  • L:4字节的确认号
  • B:1字节的「数据偏移+保留位」(前4位是TCP头长度,单位是4字节)
  • B:1字节的标志位(我们关心PSH标志,值为0x08,代表有应用层数据要交付)
  • H:2字节的窗口大小
  • H:2字节的校验和
  • H:2字节的紧急指针

示例代码:

# 跳过IP头,定位到TCP头的起始位置
tcp_header_start = ihl * 4
tcp_header = packet[tcp_header_start:tcp_header_start+20]
tcp_fields = struct.unpack('!HHLLBBHHH', tcp_header)

src_port = tcp_fields[0]
dst_port = tcp_fields[1]
# 解析TCP头实际长度(处理带选项的TCP包)
data_offset_reserved = tcp_fields[4]
tcp_header_length = (data_offset_reserved >> 4) * 4
# 检查PSH标志,确认有应用层数据
flags = tcp_fields[5]
has_payload = (flags & 0x08) != 0  # PSH标志为真,说明包里有HTTP数据

3. 提取TCP负载中的HTTP头

HTTP头是纯文本,以\r\n\r\n作为头和体的分隔符,所以我们跳过IP和TCP头后,拿到负载数据解码成字符串,再分割出头部分即可:

# 定位到TCP负载的起始位置
payload_start = tcp_header_start + tcp_header_length
payload = packet[payload_start:]

# 只处理HTTP端口(80)且有负载的包
if has_payload and (dst_port == 80 or src_port == 80):
    try:
        # 把二进制负载解码成UTF-8字符串(HTTP默认用UTF-8)
        http_data = payload.decode('utf-8')
        # 找到HTTP头的结束位置
        header_end_idx = http_data.find('\r\n\r\n')
        if header_end_idx != -1:
            http_header = http_data[:header_end_idx]
            print("提取到的HTTP头:\n", http_header)
    except UnicodeDecodeError:
        # 遇到非UTF-8编码的情况,忽略或尝试其他编码(比如gbk)
        print("无法解码为UTF-8,跳过该包")

几个关键注意点

  • 不是所有TCP包都有HTTP数据:只有PSH标志位被设置的包,才会携带应用层的HTTP请求/响应数据
  • HTTPS(443端口)的流量是加密的,没法用这种方式直接解析,初学者先从HTTP(80端口)入手
  • IP/TCP头可能带有选项:所以一定要用解析出来的ihl*4和tcp_header_length来计算头部实际长度,不要硬取20字节
  • 解码失败的情况:有些HTTP数据可能用了其他编码,遇到UnicodeDecodeError可以尝试gbk等编码,或者直接跳过

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 04:32:52