还是废话不多说,直接上代码,另外不足之处欢迎指正
# conding=utf-8
# import time
from scapy.all import *
import configparser
import re
config_load_flag = pkts_filter_flag = False
pktswithiface = []
gc = cc = sc = dict()
config = """#本文件是数据包回访配置文件,主要包含3部分:
#1、global_config:主要设置回放的参数,详细看每个参数上的注释
#2、client_config:设置客户端发送包的过滤条件和网卡
#3、server_config:设置服务器发送包的过滤条件和网卡
#回放的拓扑如下:客户端------设备--------服务器
#当然如果只是想一个网卡回放所有报文,只需server_config中与client_config设置相同网卡即可,或者client_config参数能过滤所有报文
#注意事项:1、不要删除文件内任何参数,哪怕为空 2、所有参数值不需要加引号,有空格也不需要 3、有的参数不能为空,根据参数上注释填写
#
#本地网卡列表如下:
#{ifaces}
[global_config]
#文本路径不能为空
filepath = test.pcap
#回访报文的类型:ipv4/ipv6,不能为空
ip_version = ipv4
#是否需要去除报文内vlan,方便在虚拟化环境测试,1-去除,0-不去除
vlan_remove = 1
#回放每个报文的间隔,可以为空,因为是无状态发包,所以可能会存在第1个包还没转发出去,第2个包就已经收到的情况,这种情况可以配置时间间隔,例如:0.1
interval_time =
#回放报文的轮数,填写0无限回放
loop_times = 1
#客户端发送流量信息配置,一个报文先匹配客户端过滤条件,再匹配服务器过滤条件,如都不匹配则不会回放
[client_config]
#源IP,可以为空
src = 192.169.11.85
#目的IP,可以为空
dst =
#协议类型(TCP/UDP),可以为空,在回放纯IP报文或者icmp报文时,pro、sport、dport都需要为空
pro = TCP
#源端口,可以为空
sport =
#目的端口,可以为空
dport = 20150
#回放网卡,不能为空
iface =
#服务端发送流量信息配置,注释可参考client_config
[server_config]
src = 172.16.4.206
dst =
pro = TCP
sport =
dport = 59232
iface =
"""
# 获取网卡描述列表
def getiface_desc():
interfaces = []
for key, value in conf.ifaces.data.items():
interfaces.append(value.description)
return interfaces
# 获取网卡名称列表
def getiface_name():
return [value.name for key, value in conf.ifaces.data.items()]
def create_configfile(config):
config = config.format(ifaces=getiface_name())
with open("pktreplay.conf", mode="w", encoding="utf-8") as pf:
pf.write(config)
print("配置文件生成完毕,文件名:pktreplay.conf,请根据文件内说明按需修改")
# 包过滤
def is_correct_pkt(pkt: scapy.layers.l2.Ether, five_tuples: dict) -> bool:
if pkt.haslayer('IP'):
if five_tuples["src"] == "" or pkt["IP"].src == five_tuples["src"]:
if five_tuples["dst"] == "" or pkt["IP"].dst == five_tuples["dst"]:
if five_tuples["pro"] == "":
if five_tuples["sport"] == "":
if five_tuples["dport"] == "":
return True
elif pkt.haslayer("TCP") and pkt["TCP"].dport == five_tuples["dport"]:
return True
elif pkt.haslayer("UDP") and pkt["UDP"].dport == five_tuples["dport"]:
return True
elif pkt.haslayer("TCP") and pkt["TCP"].sport == five_tuples["sport"]:
if five_tuples["dport"] == "":
return True
elif pkt["TCP"].dport == five_tuples["dport"]:
return True
elif pkt.haslayer("UDP") and pkt["UDP"].sport == five_tuples["sport"]:
if five_tuples["dport"] == "":
return True
elif pkt["UDP"].dport == five_tuples["dport"]:
return True
elif pkt.haslayer(five_tuples["pro"]):
if five_tuples["sport"] == "" or pkt[five_tuples["pro"]].sport == five_tuples["sport"]:
if five_tuples["dport"] == "":
return True
elif pkt[five_tuples["pro"]].dport == five_tuples["dport"]:
return True
return False
def is_correct_ipv6_pkt(pkt: scapy.layers.l2.Ether, five_tuples: dict) -> bool:
if not pkt.haslayer('IPv6'):
return False
if five_tuples.get("src") != "" and pkt["IPv6"].src != five_tuples.get("src"):
return False
if five_tuples.get("dst") != "" and pkt["IPv6"].src != five_tuples.get("src"):
return False
if five_tuples["pro"] == "":
if five_tuples["sport"] == "":
if five_tuples["dport"] == "":
return True
elif pkt.haslayer("TCP") and pkt["TCP"].dport == five_tuples["dport"]:
return True
elif pkt.haslayer("UDP") and pkt["UDP"].dport == five_tuples["dport"]:
return True
elif pkt.haslayer("TCP") and pkt["TCP"].sport == five_tuples["sport"]:
if five_tuples["dport"] == "":
return True
elif pkt["TCP"].dport == five_tuples["dport"]:
return True
elif pkt.haslayer("UDP") and pkt["UDP"].sport == five_tuples["sport"]:
if five_tuples["dport"] == "":
return True
elif pkt["UDP"].dport == five_tuples["dport"]:
return True
elif pkt.haslayer(five_tuples["pro"]):
if five_tuples["sport"] == "" or pkt[five_tuples["pro"]].sport == five_tuples["sport"]:
if five_tuples["dport"] == "":
return True
elif pkt[five_tuples["pro"]].dport == five_tuples["dport"]:
return True
return False
def pkt_replay_with_sleep(x, pktswithiface, gc):
pkts_len = len(pktswithiface)
y = 0
for pkt, iface in pktswithiface:
sendp(pkt, iface=iface, verbose=0)
y += 1
z = int(y / pkts_len * 100)
print("\r[%-100s]第%d轮-%d%%" % (z * "#", x, z), end="")
if gc["interval_time"] != "":
time.sleep(gc["interval_time"])
def pkt_replay(pktswithiface, gc):
print(f'总计循环{gc["loop_times"]}轮,每轮回访{len(pktswithiface)}个报文,开始回放报文:')
if gc["loop_times"] == 0:
x = 1
while True:
pkt_replay_with_sleep(x, pktswithiface, gc)
x += 1
else:
for x in range(1, gc["loop_times"] + 1):
pkt_replay_with_sleep(x, pktswithiface, gc)
print("\n")
def port_str_to_int(config_dict):
config_dict["sport"] = int(config_dict["sport"]) if config_dict["sport"] != "" else ""
config_dict["dport"] = int(config_dict["dport"]) if config_dict["dport"] != "" else ""
return config_dict
def config_load():
conf = configparser.ConfigParser()
conf.read("pktreplay.conf", encoding="utf-8")
global_config = dict(conf.items('global_config'))
client_config = dict(conf.items('client_config'))
server_config = dict(conf.items('server_config'))
global_config["loop_times"] = int(global_config["loop_times"])
global_config["interval_time"] = float(global_config["interval_time"]) if global_config["interval_time"] != "" else ""
client_config = port_str_to_int(client_config)
server_config = port_str_to_int(server_config)
print("配置文件读取成功!")
return global_config, client_config, server_config
def remove_vlan(pkt):
pkt_com_str = re.sub('chksum=\d+,', '', pkt.command())
pkt_com_str = re.sub('/Dot1Q\(.*?\)', '', pkt_com_str)
return eval(pkt_com_str)
def pkts_filter(gc, cc, sc):
pkts = rdpcap(gc.get("filepath"))
pkt_with_inf_list = []
if gc["ip_version"] == "ipv4":
if gc["vlan_remove"] == "1":
for pkt in pkts:
if is_correct_pkt(pkt, cc):
pkt = remove_vlan(pkt)
pkt_with_inf_list.append((pkt, cc["iface"]))
elif is_correct_pkt(pkt, sc):
pkt = remove_vlan(pkt)
pkt_with_inf_list.append((pkt, sc["iface"]))
else:
for pkt in pkts:
if is_correct_pkt(pkt, cc):
pkt_with_inf_list.append((pkt, cc["iface"]))
elif is_correct_pkt(pkt, sc):
pkt_with_inf_list.append((pkt, sc["iface"]))
elif gc["ip_version"] == "ipv6":
if gc["vlan_remove"] == "1":
for pkt in pkts:
if is_correct_ipv6_pkt(pkt, cc):
pkt = remove_vlan(pkt)
pkt_with_inf_list.append((pkt, cc["iface"]))
elif is_correct_ipv6_pkt(pkt, sc):
pkt = remove_vlan(pkt)
pkt_with_inf_list.append((pkt, sc["iface"]))
else:
for pkt in pkts:
if is_correct_ipv6_pkt(pkt, cc):
pkt_with_inf_list.append((pkt, cc["iface"]))
elif is_correct_ipv6_pkt(pkt, sc):
pkt_with_inf_list.append((pkt, sc["iface"]))
print("数据包过滤完成,报文数量为:", len(pkt_with_inf_list))
return pkt_with_inf_list
def start_replay():
global pktswithiface
global config_load_flag
global pkts_filter_flag
global gc, cc, sc
gc, cc, sc = config_load()
config_load_flag = True
pktswithiface = pkts_filter(gc, cc, sc)
pkts_filter_flag = True
pkt_replay(pktswithiface, gc)
def start_replay_without_configload():
global config_load_flag, pkts_filter_flag, pktswithiface, gc
if config_load_flag and pkts_filter_flag:
pkt_replay(pktswithiface, gc)
elif not config_load_flag:
print("还未加载过配置文件,请先执行步骤2!")
elif not pkts_filter_flag:
print("过滤报文出错,请再尝试执行一次步骤2!")
def main():
while True:
try:
start_line = """\n1、生成初始配置文件\n2、加载配置文件并回放报文\n3、仅回放报文(需要加载过配置)\n请输入选择【1-3】:"""
num = input(start_line)
match num:
case "1":
global config
create_configfile(config)
case "2":
start_replay()
case "3":
start_replay_without_configload()
case _:
print("输入有误,请重新输入!")
except UnicodeDecodeError:
pass
except (configparser.NoSectionError, ValueError, KeyError) as e:
print("请检查配置文件:", e)
except KeyboardInterrupt:
print("\n强制停止回放!")
except Exception as e:
print("发生错误!",e)
if __name__ == '__main__':
main()