界面 - 新 logo“七层”与 favicon;左侧图标菜单 + 顶栏面包屑 / 功能搜索;淡灰紫底色 + 白色圆角卡片 - 登录页、安装向导改为左侧等距插画 + 右侧圆角卡片 - 主机监控分为 在线终端 / 全部终端 / DHCP 租约 / 命名管理 新功能 - 全网概览仪表盘:路由器状态、网络健康指数(逐项扣分与建议)、在线终端、DHCP 地址池、WAN 线路状态、历史在线数 - 终端在线时长、连接数;DHCP 租约列表;应用实时速率;顶栏搜索;CSV 导出 - RouterOS 只读账号额外读取地址池、DHCP 服务器 / 客户端、PPPoE 客户端 - 启动时自动为旧库补新增的列 应用识别(原 0.1.5) - 导入 v2fly/domain-list-community(MIT),约 2800 个应用、3.8 万条域名 - SNI 识别:TLS ClientHello 与 QUIC Initial(RFC 9001/9369)解析,按连接识别 - 域名查找改为 连接 SNI → 终端 DNS/SNI → 全网 IP 表 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
4.0 KiB
Python
82 lines
4.0 KiB
Python
"""向本机 ROS-DPI 发送模拟的 DNS 应答(TZSP)和 IPFIX 流量,用于开发调试和演示。
|
||
|
||
用法:python scripts/simulate.py [--host 127.0.0.1] [--minutes 5] [--hosts 14]
|
||
需在 backend/.venv 环境下运行(借用 tests/packets.py 的构包函数)。
|
||
"""
|
||
import argparse
|
||
import os
|
||
import random
|
||
import socket
|
||
import sys
|
||
import time
|
||
|
||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend", "tests"))
|
||
from packets import client_hello, ipfix, tcp_segment, tls_record, tzsp_dns # noqa: E402
|
||
|
||
# (域名, 典型下行 MB/分钟, 上行比例)
|
||
SITES = [
|
||
("v26.douyinvod.com", 60, .03), ("upos-sz-mirrorcos.bilivideo.com", 80, .02), ("api.bilibili.com", 2, .2),
|
||
("szshort.weixin.qq.com", 3, .4), ("mmbiz.qpic.cn", 6, .05), ("rr3---sn.googlevideo.com", 90, .02),
|
||
("steamcdn-a.akamaihd.net.steamcontent.com", 120, .01), ("music.163.com", 4, .05),
|
||
("chatgpt.com", 1.5, .3), ("claude.ai", 1.2, .35), ("github.com", 3, .1), ("swcdn.apple.com", 70, .01),
|
||
("dl.delivery.mp.microsoft.com", 50, .01), ("pan.baidu.com", 30, .3), ("www.taobao.com", 5, .08),
|
||
("img.alicdn.com", 8, .03), ("www.zhihu.com", 3, .08), ("xiaohongshu.com", 9, .05),
|
||
("cdn.example-unknown.net", 6, .1), ("static.some-cdn.io", 4, .1), ("io.mi.com", .2, .5),
|
||
("tuyacn.com", .1, .5), ("www.netflix.com", 2, .05), ("ipv4-c001.nflxvideo.net", 70, .01),
|
||
("www.toutiao.com", 5, .05), ("weread.qq.com", 1, .1), ("mbank.cmbchina.com", .6, .2), ("kimi.moonshot.cn", 1, .3),
|
||
("finder.video.qq.com", 25, .02), ("open.ys7.com", 3, .6),
|
||
]
|
||
DEVICES = ["iPhone", "MacBook", "iPad", "小米电视", "Win 台式机", "Switch", "安卓手机", "NAS", "摄像头", "扫地机"]
|
||
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--host", default="127.0.0.1")
|
||
ap.add_argument("--ipfix-port", type=int, default=2055)
|
||
ap.add_argument("--tzsp-port", type=int, default=37008)
|
||
ap.add_argument("--minutes", type=float, default=3)
|
||
ap.add_argument("--hosts", type=int, default=14)
|
||
ap.add_argument("--fast", action="store_true", help="不等待,一次性发完")
|
||
ap.add_argument("--no-sni", action="store_true", help="不发 TLS 握手(只靠 DNS 识别)")
|
||
a = ap.parse_args()
|
||
|
||
rnd = random.Random(42)
|
||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||
ips = {d: f"203.0.113.{i + 10}" for i, (d, *_x) in enumerate(SITES)}
|
||
hosts = [(f"192.168.88.{100 + i}", f"AA:BB:CC:00:00:{i:02X}") for i in range(a.hosts)]
|
||
# 每台终端偏好几个站点
|
||
prefs = {ip: rnd.sample(SITES, k=rnd.randint(3, 8)) for ip, _ in hosts}
|
||
|
||
for d, ip in ips.items():
|
||
sock.sendto(tzsp_dns(hosts[0][0], d, [ip]), (a.host, a.tzsp_port))
|
||
print(f"已发送 {len(ips)} 条 DNS 应答")
|
||
|
||
end = time.time() + a.minutes * 60
|
||
rounds = 0
|
||
while True:
|
||
flows = []
|
||
for ip, mac in hosts:
|
||
for d, mb, up_ratio in prefs[ip]:
|
||
down = int(mb * 1024 * 1024 / 6 * rnd.uniform(.3, 1.6)) # 10 秒一轮
|
||
up = int(down * up_ratio)
|
||
port, cport = 443, rnd.randint(30000, 60000)
|
||
if not a.no_sni and rnd.random() < .7:
|
||
# 模拟 RouterOS 镜像过来的连接首包(TLS ClientHello)
|
||
sock.sendto(tcp_segment(ip, ips[d], cport, port, rnd.randint(1, 2**31), tls_record(client_hello(d))),
|
||
(a.host, a.tzsp_port))
|
||
flows.append(dict(src=ip, dst=ips[d], sport=cport, dport=port, proto=6, nbytes=up, mac=mac))
|
||
flows.append(dict(src=ips[d], dst=ip, sport=port, dport=cport, proto=6, nbytes=down))
|
||
for i in range(0, len(flows), 20):
|
||
sock.sendto(ipfix(flows[i:i + 20], seq=rounds), (a.host, a.ipfix_port))
|
||
rounds += 1
|
||
if a.fast and rounds >= a.minutes * 6 or not a.fast and time.time() > end:
|
||
break
|
||
if not a.fast:
|
||
time.sleep(10)
|
||
print(f"已发送 {rounds} 轮流量,共 {len(hosts)} 台终端")
|
||
print("终端建议备注名:", ", ".join(DEVICES))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|