2. Scapy环境搭建与核心对象

Scapy 的工程化使用从环境开始。与许多“装完即用”的库不同,Scapy 的能力边界高度依赖运行环境:是否具备原始套接字权限、是否安装了 libpcap、Python 版本是否匹配、是否在虚拟环境中隔离依赖,都会直接影响后续发包、嗅探与协议构造能否成功。本节围绕“装得上、跑得通、看得懂对象”三个目标展开。

一、安装与权限配置

推荐使用 Python 3.9 及以上版本,并在虚拟环境中安装,避免与系统包管理器冲突。基础安装命令如下:

python -m venv scapy-env
source scapy-env/bin/activate        # Windows 使用 scapy-env\Scripts\activate
pip install scapy
# 需要图形化抓包与部分链路层能力时,额外安装
pip install scapy[complete]

在 Linux 上,嗅探与二层发送通常需要 CAP_NET_RAW 能力。最直接的方式是以 root 运行,但工程上更推荐给 Python 解释器赋权:

sudo setcap cap_net_raw,cap_net_admin=eip $(readlink -f $(which python3))

Windows 需要安装 Npcap(勾选 WinPcap API 兼容模式),否则 sniff() 与二层发送会失败。macOS 自带 BPF 设备,一般无需额外驱动,但需注意系统完整性保护对部分接口的限制。安装完成后,用一条最小命令验证环境:

from scapy.all import *
print(conf.iface)          # 查看默认网卡
print(version)             # 查看 Scapy 版本
ans, unans = sr(IP(dst="192.168.1.1")/ICMP(), timeout=2)
ans.summary()

若 sr() 返回 ans 非空,说明收发链路已通;若全部进入 unans,优先排查网卡选择、防火墙与目标可达性,而不是怀疑协议构造。

二、交互式与脚本模式

Scapy 提供两种典型使用方式。交互式模式通过 scapy 命令进入,自带 conf、ls()、lsc() 等辅助函数,适合探索协议字段、临时验证构造结果。脚本模式则是把逻辑写入 .py 文件,便于版本管理与自动化调度。

交互式下最常用的三个探索命令是:ls(IP) 查看某层的全部字段与默认值;ls() 列出所有可用协议层;lsc() 列出所有发送/嗅探类函数。脚本模式中应显式导入所需层,避免 from scapy.all import * 带来的命名污染:

from scapy.layers.inet import IP, TCP, ICMP
from scapy.layers.l2 import Ether, ARP
from scapy.sendrecv import sr1

三、Packet 与 Layer 对象模型

Scapy 的核心抽象是“包由层叠加而成”。一个 Packet 对象内部维护一个层列表,每层是一个 Layer 子类实例,层与层之间通过 / 运算符连接。构造 IP()/TCP() 时,Scapy 并不会立即生成字节流,而是先建立对象树;只有在发送或调用 bytes() 时才逐层序列化。

理解对象模型的关键在于三个操作:show() 以缩进形式打印各层字段;summary() 输出一行摘要;haslayer() 与 getlayer() 用于判断和提取特定层。例如:

pkt = Ether()/IP(dst="10.0.0.5")/TCP(dport=443, flags="S")
pkt.show()
pkt.haslayer(TCP)        # True
pkt[TCP].dport           # 443
pkt[IP].ttl = 64         # 修改字段后重新序列化即生效

字段赋值遵循“后写覆盖”原则:同一层重复设置同一字段,以最后一次为准;不同层之间字段名可以相同(如 len),访问时需通过 pkt[层名].字段 明确指定,否则会命中第一个匹配层。

Packet 对象模型:层叠加与序列化 构造阶段(对象树) Packet 层列表 [Ether, IP, TCP] Ether dst / src / type IP dst / src / ttl / proto TCP sport / dport / flags / seq bytes() 序列化 发送阶段(字节流) Ethernet 帧 Ether 头 (14 B) IP 头 (20 B) TCP 头 (20 B) Payload 关键操作: pkt.show() 查看层结构 | pkt[IP].ttl = 64 修改字段 | pkt.haslayer(TCP) 判断层 | bytes(pkt) 生成字节流 注意:字段修改后需重新序列化才反映到字节流;同名字段用 pkt[层名].字段 明确访问。

四、常用协议层导入与构造方法

Scapy 按协议族组织模块,工程中应养成按需导入的习惯。常用导入路径如下表所示:

协议族 导入路径 典型层
链路层scapy.layers.l2Ether, ARP, Dot1Q
网络层scapy.layers.inetIP, ICMP, TCP, UDP
IPv6scapy.layers.inet6IPv6, ICMPv6ND_NS
应用层scapy.layers.dns / httpDNS, DNSQR, HTTP
无线scapy.layers.dot11Dot11, Dot11Beacon

构造方法遵循“自下而上”的书写顺序,但 Scapy 会自动推导部分字段。例如构造一个 DNS 查询:

from scapy.layers.inet import IP, UDP
from scapy.layers.dns import DNS, DNSQR

pkt = IP(dst="8.8.8.8")/UDP(dport=53)/DNS(rd=1, qd=DNSQR(qname="example.com"))
resp = sr1(pkt, timeout=3)
resp[DNS].summary()

这里 IP.proto 会由 UDP 层自动填充为 17,UDP.len 也会在序列化时计算。若手动指定了与下层冲突的值,Scapy 默认以显式赋值为准,因此排查异常时应先用 show() 确认最终字段值,而不是只看构造语句。

实践建议:把常用层导入、默认超时、默认网卡封装成一个 scapy_base.py 模块,在项目内统一引用。这样既避免重复导入,也便于在 CI 中通过环境变量切换网卡与超时参数。
常见坑:在 Windows 上未安装 Npcap 时,sr() 可能静默返回空结果而不报错;在容器中运行时,若未加 --cap-add=NET_RAW,嗅探会直接失败。遇到“构造正确但收不到回包”,先确认权限与网卡,再检查协议字段。