819 lines
27 KiB
Markdown
819 lines
27 KiB
Markdown
# 天璇 (TianXuan) 分析工作流
|
||
|
||
> 从原始 TLS 流 CSV 到实体画像、聚类结果、区分特征的完整流水线文档。
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [数据准备](#1-数据准备)
|
||
2. [数据加载与配置](#2-数据加载与配置)
|
||
3. [数据校验](#3-数据校验)
|
||
4. [数据清洗](#4-数据清洗)
|
||
5. [实体识别](#5-实体识别)
|
||
6. [类型感知聚合](#6-类型感知聚合)
|
||
7. [聚类分析](#7-聚类分析)
|
||
8. [特征提取](#8-特征提取)
|
||
9. [可视化](#9-可视化)
|
||
10. [LLM 集成](#10-llm-集成)
|
||
11. [跨机部署](#11-跨机部署)
|
||
|
||
---
|
||
|
||
## 1. 数据准备
|
||
|
||
### 1.1 CSV 格式要求
|
||
|
||
天璇接收以 **CSV 格式**存放的 TLS 流记录文件。每行代表一条网络流。
|
||
|
||
| 要求 | 说明 |
|
||
|------|------|
|
||
| **分隔符** | 逗号 `,`(默认),支持自定义分隔符 |
|
||
| **文件扩展名** | `.csv` |
|
||
| **多文件** | 支持 glob 通配符、`**/*.csv` 递归扫描、自动合并 |
|
||
| **压缩包** | `.zip` 文件自动解压(通过上传界面) |
|
||
| **文件数量** | 无硬性限制(合并时使用 Polars `diagonal_relaxed` 模式) |
|
||
|
||
### 1.2 编码建议
|
||
|
||
| 编码 | BOM 标记 | 自动检测 |
|
||
|------|---------|---------|
|
||
| **UTF-8**(推荐) | 无 / `EF BB BF` | ✅ 默认 |
|
||
| UTF-8 with BOM | `EF BB BF` | ✅ 自动检测 |
|
||
| UTF-16LE | `FF FE` | ✅ 自动检测 |
|
||
| UTF-16BE | `FE FF` | ✅ 自动检测 |
|
||
| Latin-1 | 无 | ✅ 手动指定 |
|
||
|
||
> **⚠️ 生产建议**:始终使用 **UTF-8 无 BOM** 编码。跨机器部署时强烈建议通过 `set PYTHONUTF8=1` 强制 Python 使用 UTF-8 模式(`run.bat` 已包含该设置)。
|
||
|
||
### 1.3 推荐列
|
||
|
||
系统基于列名关键词自动识别以下维度:
|
||
|
||
| 维度 | 推荐列名(不区分大小写) |
|
||
|------|------------------------|
|
||
| **源 IP** | `src_ip`、`source_ip`、`src_addr` |
|
||
| **目的 IP** | `dst_ip`、`dest_ip`、`destination_ip`、`dst_addr`、`ip_dst` |
|
||
| **字节数(上行)** | `bytes_sent`、`src_bytes`、`bytes_out`、`up_bytes`、`upload` |
|
||
| **字节数(下行)** | `bytes_rev`、`bytes_received`、`dst_bytes`、`down_bytes`、`download` |
|
||
| **持续时间** | `duration`、`dur`、`elapsed`、`time_delta`、`flow_duration` |
|
||
| **包数** | `packets`、`pkts`、`packet_count`、`total_packets` |
|
||
| **TLS 版本** | `tls_version`、`version`、`ssl_version`、`tlsver` |
|
||
| **加密套件** | `cipher_suite`、`cipher`、`tls_cipher`、`ssl_cipher` |
|
||
| **SNI** | `sni`、`server_name`、`tls_sni` |
|
||
| **协议** | `proto`、`protocol`、`l4_proto`、`ip_proto` |
|
||
| **目的端口** | `dst_port`、`destination_port`、`dest_port`、`port_dst` |
|
||
| **目标 URL** | `dst_url`、`url`、`uri` |
|
||
| **时间戳** | `timestamp`、`ts`、`time`、`datetime`、`first_seen` |
|
||
| **纬度** | `lat`、`latitude`、`latitud`、`y` |
|
||
| **经度** | `lon`、`lng`、`longitude`、`longitud`、`x` |
|
||
|
||
> 列名通过下划线分词匹配关键词(如 `flow_duration` 匹配 `duration`)。匹配算法见 `analysis/entity_aggregator.py` 中的 `_find_column()`。
|
||
|
||
### 1.4 命名规范
|
||
|
||
- **不要求**列名在所有文件中完全一致(默认 `schema_strict=false`)
|
||
- 不同名的列自动取并集,缺失列填 `null`
|
||
- 建议团队内统一列名,以获得最佳的自动识别效果
|
||
|
||
---
|
||
|
||
## 2. 数据加载与配置
|
||
|
||
### 2.1 加载入口
|
||
|
||
**主函数**:`analysis/data_loader.py::load_csv_directory()`
|
||
|
||
```python
|
||
lf, schema, row_count, file_count, memory_mb = load_csv_directory(
|
||
glob_pattern="data/*.csv",
|
||
encoding="utf-8",
|
||
delimiter=",",
|
||
schema_strict=False,
|
||
recursive=False,
|
||
)
|
||
```
|
||
|
||
**MCP 工具**(通过 `tool_registry.py` 暴露):
|
||
|
||
```
|
||
load_data(csv_glob="data/*.csv", schema_strict=False, recursive=False)
|
||
```
|
||
|
||
**CLI 管道**(一键运行):
|
||
|
||
```bash
|
||
runtime\python\python.exe manage.py run_pipeline data/*.csv
|
||
```
|
||
|
||
### 2.2 列类型配置
|
||
|
||
在 `config/config.yaml` 中通过 `columns` 映射手动指定列类型。配置优先级高于自动推断:
|
||
|
||
```yaml
|
||
columns:
|
||
src_ip: ipv4 # 强制识别为 IPv4
|
||
dst_ip: ipv4
|
||
bytes_sent: int # 强制为整数类型
|
||
bytes_rev: int
|
||
duration: float # 强制为浮点类型
|
||
tls_version: enum # 枚举类型(低基数分类变量)
|
||
cipher_suite: hex # 十六进制数据
|
||
dst_url: url # URL 字符串
|
||
lat: lat_lon # 地理纬度
|
||
lon: lat_lon # 地理经度
|
||
timestamp: timestamp # 时间戳
|
||
```
|
||
|
||
支持的类型字符串及映射:
|
||
|
||
| 配置值 | `DataType` | 聚合方式 |
|
||
|--------|-----------|---------|
|
||
| `int` | `INT` | mean / sum |
|
||
| `float` | `FLOAT` | mean / sum |
|
||
| `enum` | `ENUM` | mode(众数) |
|
||
| `hex` | `HEX` | count(hex 对数统计) |
|
||
| `url` | `URL` | domain(提取域名) |
|
||
| `ipv4` | `IPv4` | subnet(/24 网络) |
|
||
| `lat_lon` | `LAT_LON` | mean |
|
||
| `timestamp` | `TIMESTAMP` | min / max |
|
||
| `bool_enum` | `BOOL_ENUM` | mode |
|
||
| `string` | `STRING` | count |
|
||
|
||
### 2.3 Schema 容错
|
||
|
||
| 模式 | 行为 | 适用场景 |
|
||
|------|------|---------|
|
||
| `schema_strict: true` | 列名不一致时报 `ValueError` | 测试/质量门禁 |
|
||
| `schema_strict: false`(默认) | 自动并集合并,缺失列填 null | 日常使用 |
|
||
|
||
**建议**:生产环境建议 `schema_strict: true`,提前发现列名漂移。
|
||
|
||
### 2.4 GeoIP 配置
|
||
|
||
`config.yaml` 中的 GeoIP 配置段控制自动经纬度填充和旧列丢弃:
|
||
|
||
```yaml
|
||
data:
|
||
geoip:
|
||
enabled: true
|
||
ip_columns: ["src_ip", "dst_ip"] # 需查询的 IP 列
|
||
drop_columns: ["src_latitude", "src_longitude", # 加载后丢弃的旧列
|
||
"dst_latitude", "dst_longitude"]
|
||
```
|
||
|
||
GeoIP 数据文件:`data/geoip_data.txt`,内嵌约 23 个主要城市 IP 段(200 KB 二进制查询格式)。
|
||
|
||
查询接口:
|
||
|
||
```python
|
||
from analysis.geoip import lookup
|
||
result = lookup("8.8.8.8") # → {lat: 37.386, lon: -122.083, city: "Mountain View", country: "US"}
|
||
```
|
||
|
||
> ⚠️ **旧列丢弃**:如果 CSV 本身已包含 `src_latitude`/`src_longitude` 等列(来自旧有系统的 GeoIP 结果),系统在加载后自动丢弃这些列,避免与新的 GeoIP 查询冲突。
|
||
|
||
---
|
||
|
||
## 3. 数据校验
|
||
|
||
### 3.1 校验入口
|
||
|
||
**主函数**:`analysis/data_validator.py::validate()`
|
||
|
||
```python
|
||
from analysis.data_validator import validate, report
|
||
|
||
# 返回 JSON 报告
|
||
validation_result = validate("dataset_id", strict=False)
|
||
|
||
# 返回格式化字符串报告
|
||
print(report("dataset_id"))
|
||
```
|
||
|
||
校验项通过 `[VALIDATE]` 日志标记输出。
|
||
|
||
### 3.2 校验项说明
|
||
|
||
| 校验项 | 检测内容 | 判定标准 |
|
||
|--------|---------|---------|
|
||
| **列类型矛盾** | schema 推断类型 vs classify_column 结果 | `INT↔FLOAT`、`ENUM↔BOOL_ENUM` 为良性差异;STRING 类型包容一切 |
|
||
| **缺失率** | 每列 `null_count / total_rows` | `>50%` → **高风险**;`>30%` → 警告 |
|
||
| **异常值** | 数值列 Z-score > 5 的比例 | `>5%` 触发警告 |
|
||
| **枚举分布** | ENUM/BOOL_ENUM 列的值频率 | 标记空白、`+`、`-` 等特殊符号 |
|
||
| **IPv4 有效性** | IP 列每个值的合法性 | 无效 IP 比例 `>50%` → **高风险** |
|
||
|
||
### 3.3 校验报告解读
|
||
|
||
JSON 报告结构:
|
||
|
||
```json
|
||
{
|
||
"valid": true, // 无高风险项时为 true
|
||
"dataset_id": "ds_abc123",
|
||
"total_rows": 5000,
|
||
"total_columns": 24,
|
||
"columns": {
|
||
"src_ip": {
|
||
"dtype": "String",
|
||
"inferred_type": "IPv4",
|
||
"null_rate": 0.02,
|
||
"warnings": []
|
||
},
|
||
"duration": {
|
||
"dtype": "Float64",
|
||
"inferred_type": "FLOAT",
|
||
"null_rate": 0.55,
|
||
"z_score_gt_5_ratio": 0.03,
|
||
"warnings": ["null rate 55.0% exceeds 50% (high risk)"]
|
||
}
|
||
},
|
||
"warnings": [...],
|
||
"risks": ["Column 'duration': null rate 55.0% exceeds 50% (high risk)"]
|
||
}
|
||
```
|
||
|
||
### 3.4 处理建议
|
||
|
||
| 发现问题 | 建议处理 |
|
||
|---------|---------|
|
||
| 缺失率 > 50% | 检查数据源是否漏采集;考虑在预处理阶段 `fillna` 或 `drop` |
|
||
| Z-score > 5 比例过高 | 检查是否有异常流量(DDoS、扫描等) |
|
||
| 无效 IPv4 比例高 | 检查列名是否错误(非 IP 列被匹配为 IPv4) |
|
||
| 枚举值异常(空白/`+`/`-`) | 这些值会在清洗阶段被规范化(见第 4 节) |
|
||
|
||
---
|
||
|
||
## 4. 数据清洗
|
||
|
||
数据清洗在 `load_csv_directory()` 中自动执行,分三个阶段。
|
||
|
||
### 4.1 旧经纬度丢弃
|
||
|
||
加载完成后,立即丢弃 GeoIP 配置中指定的旧列:
|
||
|
||
```python
|
||
# 默认丢弃列
|
||
drop_columns = ["src_latitude", "src_longitude", "dst_latitude", "dst_longitude"]
|
||
# 只丢弃 schema 中实际存在的列
|
||
merged_lf = merged_lf.drop(existing_drop)
|
||
```
|
||
|
||
日志标记:`[CLEAN] dropped columns: [...]`
|
||
|
||
### 4.2 BOOL_ENUM 标准化
|
||
|
||
对 `classify_schema()` 标记为 `DataType.BOOL_ENUM` 的字符串列,执行以下变换:
|
||
|
||
| 原始值 | 标准化后 |
|
||
|--------|---------|
|
||
| `""`(空字符串) | `null` |
|
||
| `"+"` | `"True"` |
|
||
| `"-"` | `"False"` |
|
||
| `"t"`, `"T"`, `"true"`, `"True"` | 保持不变 |
|
||
| `"f"`, `"F"`, `"false"`, `"False"` | 保持不变 |
|
||
|
||
> 原生 Boolean 类型的列跳过此步骤(已经是标准格式)。
|
||
|
||
日志标记:`[CLEAN] BOOL_ENUM columns normalised: [...]`
|
||
|
||
### 4.3 HEX 预处理
|
||
|
||
对标记为 `DataType.HEX` 的列(如 cipher_suite),添加 `{col}_hex_pairs_count` 辅助列:
|
||
|
||
```python
|
||
# 计算每个值中空格分隔的 hex 对数量
|
||
pl.col("cipher_suite").str.split(" ").list.len().alias("cipher_suite_hex_pairs_count")
|
||
```
|
||
|
||
该计数列在后续聚合中以 `avg_hex_pairs_count` 参与聚类特征。
|
||
|
||
日志标记:`[CLEAN] HEX columns added hex_pairs_count: [...]`
|
||
|
||
### 4.4 通用数值清洗
|
||
|
||
对于任何未被分类为 IPv4/URL/HEX/ENUM/LAT_LON 的字符串列,系统自动尝试转为 Float64:
|
||
|
||
| 原始值 | 清洗后 |
|
||
|--------|--------|
|
||
| `"123.45"` | `123.45` |
|
||
| `"+"` / `"-"` | `null` |
|
||
| 空白/空字符串 | `null` |
|
||
| `"N/A"` / `"null"` / `"None"` | `null` |
|
||
| 其他不可解析字符串 | `null` |
|
||
|
||
这防止了"mean on str column"崩溃(当字符串列因数据伪影未能被早期类型检测捕获时),
|
||
无论后续数据中出现何种新伪影。
|
||
|
||
函数:`analysis/entity_aggregator.py` → `_coerce_to_float()`
|
||
|
||
日志标记:`[CLEAN] numeric coercion applied to: [...]`
|
||
|
||
---
|
||
|
||
## 5. 实体识别
|
||
|
||
### 5.1 自动检测原理
|
||
|
||
**主函数**:`analysis/entity_detector.py::detect_entity_column()`
|
||
|
||
采用三维打分算法,对所有 String/Categorical 列评分:
|
||
|
||
| 维度 | 分值 | 说明 |
|
||
|------|------|------|
|
||
| **名称匹配** | +3.0 | 列名(归一化后)精确匹配关键词或 `_` 分隔匹配 |
|
||
| **唯一值比率** | 0~10 | `unique_ratio = unique_count / non_null_count`,在 `[0.01, 0.50]` 区间内得分最高 |
|
||
| **字符串类型** | +2.0 | `Utf8` / `String` / `Categorical` 类型加分 |
|
||
| **高唯一性惩罚** | -5.0 | `unique_ratio > 0.50` 时扣分(可能是随机 ID) |
|
||
| **高空值惩罚** | ×0.1 | null 率 > 50% 时分数严重降低 |
|
||
|
||
关键词集(`ENTITY_KEYWORDS`):
|
||
|
||
```
|
||
src_ip, dst_ip, source_ip, destination_ip, ip,
|
||
sni, server_name, host, domain, user, client,
|
||
username, hostname, mac, email, session_id,
|
||
uid, id, src_addr, dst_addr
|
||
```
|
||
|
||
### 5.2 多列复合键
|
||
|
||
**支持场景**:当需要按 `(src_ip, dst_ip)` 或 `(src_ip, sni)` 等多列组合作为实体标识时。
|
||
|
||
**MCP 工具参数**:
|
||
|
||
```python
|
||
build_entity_profiles(
|
||
dataset_id="ds_...",
|
||
entity_columns=["src_ip", "dst_ip"], # 多列复合键
|
||
auto_detect=false
|
||
)
|
||
```
|
||
|
||
**CLI 管道**目前仅支持单列实体键。
|
||
|
||
### 5.3 IP 掩码聚类
|
||
|
||
在聚合阶段中,对 `DataType.IPv4` 类型的列自动生成 `/24` 子网信息:
|
||
|
||
```python
|
||
# entity_aggregator.py 中:
|
||
subnet_expr = (
|
||
pl.col(ip_col).str.split('.').list.slice(0, 3).list.join('.') + pl.lit('.0/24')
|
||
)
|
||
# → "192.168.1.0/24"
|
||
```
|
||
|
||
生成的 `unique_24_networks` 在聚类时作为特征输入,反映实体的网络分布广度。
|
||
|
||
---
|
||
|
||
## 6. 类型感知聚合
|
||
|
||
### 6.1 聚合原理
|
||
|
||
**主函数**:`analysis/entity_aggregator.py::aggregate_by_entity()`
|
||
|
||
1. 扫描 schema 通过关键词匹配找到可用列
|
||
2. 通过 `type_classifier` 获取每列的 `DataType` 分类
|
||
3. 按数据类型选择对应的聚合函数
|
||
|
||
### 6.2 数据类型→聚合函数映射
|
||
|
||
| `DataType` | 聚合方式 | 特征命名 | 代码位置 |
|
||
|-----------|---------|---------|---------|
|
||
| **INT** (整数) | `mean()` 均值 | `total_bytes_sent`, `total_packets` | `build_aggregation_params()` |
|
||
| **FLOAT** (浮点) | `mean()` 均值 | `avg_duration`, `avg_latitude` | `build_aggregation_params()` |
|
||
| **ENUM** (枚举) | `mode().first()` 众数 | `tls_version_mode`, `tcp_flag_modes` | `build_aggregation_params()` |
|
||
| **HEX** (十六进制) | `str.extract_all().list.len().mean()` 统计 hex 对数量 | `avg_hex_pairs_count` | `build_aggregation_params()` |
|
||
| **URL** (链接) | `str.extract(r'https?://([^/]+)').n_unique()` 统计唯一域名 | `unique_domains` | `build_aggregation_params()` |
|
||
| **IPv4** (地址) | `str.split('.').list.slice(0,3).join('.') + '.0/24'.n_unique()` 子网多样性 | `unique_24_networks` | `build_aggregation_params()` |
|
||
|
||
### 6.3 聚合特征分类
|
||
|
||
所有生成的聚合特征分为 6 大类:
|
||
|
||
| 类别 | 特征 | 说明 |
|
||
|------|------|------|
|
||
| **流统计** | `flow_count`、`total_bytes_sent`、`total_bytes_rev`、`avg_duration`、`total_packets` | 实体流量基本量 |
|
||
| **目标多样性** | `unique_dst_ips`、`unique_dst_ports`、`unique_dst_networks` | 目的地址分布广度 |
|
||
| **TLS 特征** | `unique_ciphers`、`tls_version_mode`、`unique_versions`、`sni_count` | 加密和证书多样性 |
|
||
| **协议特征** | `unique_protocols`、`tcp_flag_modes` | 协议和标志分布 |
|
||
| **地理位置** | `avg_latitude`、`avg_longitude` | 经纬度均值 |
|
||
| **时间模式** | `first_seen`、`last_seen`、`active_hours_count` | 活动时间段 |
|
||
|
||
### 6.4 类型安全防范
|
||
|
||
build_aggregation_params 在聚合前检查列的实际 Polars dtype:
|
||
|
||
```python
|
||
# 在聚合非数值列前 cast
|
||
if schema.get(col_name) in ('Utf8', 'String'):
|
||
pl.col(col_name).cast(pl.Float64)
|
||
```
|
||
|
||
这解决了跨机"mean on str"错误(当同一数据在不同机器上被 Polars 推断为不同 dtypes 时)。
|
||
|
||
---
|
||
|
||
## 7. 聚类分析
|
||
|
||
### 7.1 算法选择
|
||
|
||
| 算法 | 适用场景 | 优点 | 缺点 |
|
||
|------|---------|------|------|
|
||
| **HDBSCAN**(默认) | 未知簇数、存在噪声点 | 自动确定簇数;噪声点处理;无需预设 k | 对 `min_cluster_size` 敏感;大数据集计算慢 |
|
||
| **KMeans** | 已知簇数、球形分布 | 速度快;可预设 k | 需指定 n_clusters;对离群点敏感 |
|
||
|
||
### 7.2 参数建议
|
||
|
||
**HDBSCAN 参数**:
|
||
|
||
| 参数 | 默认值 | 说明 | 调整建议 |
|
||
|------|--------|------|---------|
|
||
| `min_cluster_size` | 5 | 最小簇样本数 | 数据量大时增大(10~20);数据少时自动降级为 `max(len//2, 2)` |
|
||
| `min_samples` | None | 核心点邻域 | 默认与 min_cluster_size 相同;增大产生更保守的聚类 |
|
||
| `metric` | euclidean | 距离度量 | 高维特征时考虑 `cosine` |
|
||
| `cluster_selection_epsilon` | 0.0 | 簇选择阈值 | 越大簇越少 |
|
||
|
||
**KMeans 参数**:
|
||
|
||
| 参数 | 默认值 | 说明 |
|
||
|------|--------|------|
|
||
| `n_clusters` | 3 | 簇数 |
|
||
| `random_state` | 42 | 随机种子(保证可复现性) |
|
||
|
||
### 7.3 零样本降级
|
||
|
||
当样本数过少或全为缺失值时,系统自动降级:
|
||
|
||
```python
|
||
if len(data) < 3:
|
||
return {"n_clusters": 0, "n_noise": 0, "quality_metrics": {"note": "too few samples"}}
|
||
```
|
||
|
||
同样,全部为 NaN 的列在聚类前自动丢弃。
|
||
|
||
### 7.4 预处理步骤
|
||
|
||
聚类执行前的自动预处理流程:
|
||
|
||
1. **列过滤**:只保留请求的特征列
|
||
2. **全 NaN 列丢弃**:无用列自动移除
|
||
3. **NaN 填充**:数值 NaN 用该列均值填充
|
||
4. **StandardScaler**:所有特征标准化(零均值、单位方差)
|
||
5. **min_cluster_size 自适应**:不超过样本数的 50%
|
||
|
||
### 7.5 质量评估
|
||
|
||
| 指标 | 范围 | 说明 |
|
||
|------|------|------|
|
||
| **Silhouette Score** | `[-1, 1]` | >0.5 表示良好聚类;>0.7 表示优秀 |
|
||
| **Davies-Bouldin Index** | `[0, +∞)` | 越低越好(簇内紧凑、簇间分离) |
|
||
| **Calinski-Harabasz Score** | `[0, +∞)` | 越高越好 |
|
||
| **Noise Ratio** | `[0, 1]` | HDBSCAN 特有的噪声点比例 |
|
||
|
||
### 7.6 CLI 运行示例
|
||
|
||
```bash
|
||
# 完整管道(HDBSCAN 默认)
|
||
runtime\python\python.exe manage.py run_pipeline data/complex_test.csv
|
||
|
||
# 指定 KMeans
|
||
runtime\python\python.exe manage.py run_pipeline data/*.csv --algo kmeans
|
||
|
||
# 手动指定实体列
|
||
runtime\python\python.exe manage.py run_pipeline data/*.csv --entity-col src_ip
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 子网实体聚合
|
||
|
||
config.yaml中配置:
|
||
```yaml
|
||
entity:
|
||
subnet_masks: [24, 28] # 子网掩码列表
|
||
ip_columns: ["src_ip", "dst_ip"] # 需要聚合的IP列
|
||
```
|
||
|
||
启用后,同一子网(如 10.0.1.0/24)下的所有 IP 合并为一个实体节点。
|
||
支持 /24(0-255)和 /28(240-255, 16个IP分组)两种掩码。
|
||
|
||
`entity_aggregator.ip_to_subnet()` 负责 IP 到 CIDR 子网前缀的转换,
|
||
`apply_subnet_aggregation()` 在 group_by 前对 IP 列应用子网掩码。
|
||
|
||
```python
|
||
# 示例: IP → /24 子网
|
||
ip_to_subnet("192.168.1.42", 24) # → "192.168.1.0/24"
|
||
|
||
# 示例: IP → /28 子网
|
||
ip_to_subnet("192.168.1.42", 28) # → "192.168.1.32/28"
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 特征提取
|
||
|
||
### 8.1 方法对比
|
||
|
||
**主函数**:`analysis/tool_registry.py` → `_handle_extract_features()`
|
||
|
||
| 方法 | 公式 | 适用场景 |
|
||
|------|------|---------|
|
||
| **Z-Score**(默认) | `(μ_cluster - μ_global) / σ_global` | 簇中心 vs 全局均值的偏离程度 |
|
||
| **ANOVA** | `|μ_cluster - μ_global| / (σ_cluster + σ_global)` | 考虑簇内方差,更保守 |
|
||
|
||
### 8.2 区分特征解读
|
||
|
||
返回的每个特征包含:
|
||
|
||
```json
|
||
{
|
||
"feature_name": "total_bytes_sent",
|
||
"cluster_label": 0,
|
||
"mean": 1234567.89, // 该簇均值
|
||
"std": 234567.89, // 该簇标准差
|
||
"global_mean": 987654.32, // 全局均值
|
||
"global_std": 765432.10, // 全局标准差
|
||
"distinguishing_score": 2.34, // Z-Score 或 ANOVA 分数
|
||
"distinguishing_method": "zscore"
|
||
}
|
||
```
|
||
|
||
**解读方法**:
|
||
|
||
| 分数范围 | 含义 |
|
||
|---------|------|
|
||
| \|Z\| < 1.0 | 无明显区分力 |
|
||
| 1.0 ≤ \|Z\| < 2.0 | 中等区分力 |
|
||
| \|Z\| ≥ 2.0 | **强区分特征**(在 Web 界面中以颜色高亮) |
|
||
|
||
### 8.3 地理分布可视化
|
||
|
||
如果聚合特征包含 `avg_latitude` 和 `avg_longitude`,聚类概览页会同时显示:
|
||
|
||
- **PCA 散点图**:实体在二维主成分空间的分布,颜色区分聚类
|
||
- **地理分布图**:实体在地理坐标上的分布
|
||
|
||
地理位置在 Web 界面中通过 Canvas 2D API 原生绘制(无外部 JS 依赖):
|
||
|
||
```python
|
||
# views.py 中提取逻辑
|
||
lat = _extract_lat(feature_json.get('avg_latitude'))
|
||
lon = _extract_lon(feature_json.get('avg_longitude'))
|
||
# 过滤无效值:纬度 -90~90,经度 -180~180
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 可视化
|
||
|
||
### 9.1 PCA 散点图
|
||
路径: `/clusters/<run_id>/`
|
||
使用纯 Canvas 2D 绘制的散点图,展示实体在二维主成分空间中的分布。
|
||
点颜色按聚类标签区分,鼠标悬停显示实体名称。
|
||
依赖: `run_pipeline` 步骤 6 自动计算 PCA-2D 坐标并写入 `EntityProfile` 表。
|
||
|
||
### 9.2 3D 地球流量弧线
|
||
路径: `/globe/`
|
||
使用 Three.js (603KB, 全离线) 绘制的三维地球,显示源 IP 到目标 IP 的 TLS 流量弧线。
|
||
- 颜色: TLSv1.3 (蓝) / TLSv1.2 (粉) / 其他 (青)
|
||
- 弧线高度根据距离自动调整
|
||
- 鼠标拖拽旋转地球
|
||
- 地球贴图: 1.4MB (离线内嵌)
|
||
- **多数据源叠加**: 页面复选框选择多个运行,弧线叠加在同一地球上
|
||
- **经纬度来源**: 优先使用数据中`src_latitude/src_longitude/dst_latitude/dst_longitude`列,无则回退GeoIP查询
|
||
- GeoIP: 从 `data/geoip_city.csv` 查询 IP 对应经纬度
|
||
- 滚轮缩放: 鼠标滚轮控制相机距离(3-30)
|
||
- 经纬线: 10度间隔的白色半透明辅助线
|
||
- 国境线: world_borders.js 10国精简轮廓,含台湾和南海九段线示意
|
||
- **背面剔除**: `updatePulses` 中使用 `localToWorld` 替代 `applyQuaternion`,确保弧线脉冲只在面向相机的一侧显示,不穿透地球
|
||
- **脉冲动画**: 弧线上移动的贝塞尔曲线脉冲光点,`arcGroup.localToWorld(midPt)` 计算世界坐标,`midPt.dot(camDir) < -0.08` 判段正面可见性
|
||
- 多数据选择: 复选框选择多个运行叠加弧线
|
||
- 经纬度来源: 优先使用数据列 src_latitude/src_longitude 等,无则GeoIP回退
|
||
|
||
### 9.3 聚类概览页
|
||
路径: `/clusters/<run_id>/`
|
||
同时显示 PCA 散点图和每簇的 Top 区分特征。
|
||
|
||
## 10. LLM 集成
|
||
|
||
### 9.1 API 配置
|
||
|
||
通过 `config/config.yaml` 或 Web 界面「配置」页面设置:
|
||
|
||
```yaml
|
||
llm:
|
||
enabled: true # 启用 LLM 功能
|
||
base_url: "https://api.openai.com/v1" # OpenAI 兼容 API 地址
|
||
api_key: "sk-..." # API Key
|
||
model: "deepseek-v4-flash" # 模型名称
|
||
max_tokens: 2048
|
||
temperature: 0.1
|
||
```
|
||
|
||
支持的 LLM 服务:
|
||
- OpenAI GPT-4 / GPT-3.5
|
||
- Azure OpenAI Service
|
||
- 本地部署的 Ollama / vLLM / LM Studio
|
||
- 其他 OpenAI 兼容 API
|
||
|
||
### 9.2 32B 模型优化
|
||
|
||
系统针对 32B 等较大模型的推理特性做了以下优化(`tianxuan/llm_orchestrator.py`):
|
||
|
||
| 优化项 | 实现 | 说明 |
|
||
|--------|------|------|
|
||
| **Step Guidance** | 每步告诉模型下一步做什么 | 减少 32B 模型在复杂推理中的发散 |
|
||
| **工具结果截断** | 截断至 1200 chars | 避免上下文窗口溢出 |
|
||
| **超时** | 90s | 32B 推理更慢,比默认超时长 |
|
||
| **最大步数** | 15 步 | 限制过长的工具调用链 |
|
||
| **工具描述** | 一行简洁描述 | 减少冗长描述对 token 的消耗 |
|
||
|
||
### 9.3 LLM 分析流程
|
||
|
||
自动化编排调用链:
|
||
|
||
```
|
||
Step 1: profile_data("ds_...") → 了解数据集的列统计
|
||
Step 2: build_entity_profiles("ds_...") → 实体聚合
|
||
Step 3: run_clustering("entity_...") → 聚类分析
|
||
Step 4: extract_features("entity_...") → 特征提取
|
||
Step 5: 总结分析结果
|
||
```
|
||
|
||
### 9.4 日志链解读
|
||
|
||
LLM 交互日志以 `[LLM]` 和 `[TOOL]` 标记输出到 `logs/tianxuan.log`:
|
||
|
||
```
|
||
[LLM] model=deepseek-v4-flash # 模型名
|
||
[LLM] latency_ms=3245 # LLM 响应延迟
|
||
[LLM REQUEST] ... # 请求内容(缩减后)
|
||
[LLM RESPONSE] ... # 响应状态
|
||
[TOOL] profile_data # LLM 调用的工具
|
||
[TOOL] build_entity_profiles
|
||
[TOOL] run_clustering
|
||
[TOOL] extract_features
|
||
```
|
||
|
||
### 9.5 故障排查
|
||
|
||
| 问题 | 常见原因 | 排查方法 |
|
||
|------|---------|---------|
|
||
| 400 错误 | API 地址错误 / Key 无效 | 配置页「测试连通性」 |
|
||
| 超时 | 32B 推理慢 / 网络延迟 | 检查 `latency_ms`;增大 `max_tokens` |
|
||
| 工具调用失败 | LLM 返回格式错误 | 检查 `[TOOL]` 日志看哪个工具调用失败 |
|
||
| 分析结果为空 | 实体检测失败 | 检查实体列是否正确,尝试手动指定 |
|
||
|
||
**连通性测试**:
|
||
|
||
```bash
|
||
# CLI 测试
|
||
runtime\python\python.exe scripts\test_llm_full.py
|
||
|
||
# Web 测试
|
||
# 导航到「配置」页面 → 填写 LLM 配置 → 点击「测试连通性」
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 跨机部署
|
||
|
||
### 10.1 运行时隔离验证
|
||
|
||
**脚本**:`scripts/verify_runtime.py`
|
||
|
||
在不同机器上运行该脚本,输出应完全一致:
|
||
|
||
```bash
|
||
# 每台机器各自执行
|
||
runtime\python\python.exe scripts\verify_runtime.py
|
||
```
|
||
|
||
验证内容:
|
||
|
||
| 检查项 | 期望值 |
|
||
|--------|--------|
|
||
| Python 版本 | 3.12.x |
|
||
| ENABLE_USER_SITE | False(隔离) |
|
||
| django | 4.2.x |
|
||
| polars | 1.42.x |
|
||
| sklearn | 1.5.x |
|
||
| numpy | 1.26.x |
|
||
| defaultencoding | utf-8 |
|
||
| filesystemencoding | utf-8 |
|
||
|
||
### 10.2 diagnose_compare 使用
|
||
|
||
**脚本**:`scripts/diagnose_compare.py`
|
||
|
||
对比两台机器的运行日志,定位第一个差异点:
|
||
|
||
```bash
|
||
# 每台机器运行管道,生成日志
|
||
del logs\tianxuan.log
|
||
runtime\python\python.exe manage.py run_pipeline data\complex_test.csv
|
||
copy logs\tianxuan.log log_机器A.txt
|
||
|
||
# 在开发机上对比
|
||
runtime\python\python.exe scripts\diagnose_compare.py log_开发机.txt log_机器A.txt
|
||
```
|
||
|
||
输出示例:
|
||
|
||
```
|
||
[DIFF] 首个差异在行 123:
|
||
--- 机器A ---
|
||
[LOAD_SCHEMA] col=duration dtype=Utf8 sample=1.2s,0.5s,3.1s
|
||
--- 机器B ---
|
||
[LOAD_SCHEMA] col=duration dtype=Float64 sample=1.2,0.5,3.1
|
||
```
|
||
|
||
首个 `[DIFF]` 行就是问题根源。
|
||
|
||
### 10.3 关键日志标记
|
||
|
||
| 标记 | 对应文件 | 含义 |
|
||
|------|---------|------|
|
||
| `[LOAD]` | `data_loader.py` | CSV 加载完成 |
|
||
| `[LOAD_SCHEMA]` | `data_loader.py` | 每列的推断类型(最重要!) |
|
||
| `[CLEAN]` | `data_loader.py` | 清洗操作记录 |
|
||
| `[VALIDATE]` | `data_validator.py` | 数据校验结果 |
|
||
| `[FIND_COL]` | `entity_aggregator.py` | 关键词匹配结果 |
|
||
| `[CLUSTER_INPUT]` | `tool_registry.py` | 聚类输入形状 |
|
||
| `[GEOIP]` | `geoip.py` | GeoIP 数据加载 |
|
||
| `[LLM]` | `llm_orchestrator.py` | LLM 调用状态 |
|
||
| `[TOOL]` | `llm_orchestrator.py` | LLM 调用的工具 |
|
||
| `[DB_SAVE_ERROR]` | `tool_registry.py` | 数据库持久化错误 |
|
||
|
||
### 10.4 跨机一致性检查清单
|
||
|
||
当两台机器表现不一致时,逐项排查:
|
||
|
||
```bash
|
||
# 1. Python 版本
|
||
runtime\python\python.exe --version
|
||
|
||
# 2. 编码设置
|
||
runtime\python\python.exe -c "
|
||
import sys; print(f'defaultenc={sys.getdefaultencoding()}')
|
||
print(f'fsenc={sys.getfilesystemencoding()}')
|
||
print(f'utf8_mode={sys.flags.utf8_mode}')
|
||
"
|
||
|
||
# 3. Polars 版本
|
||
runtime\python\python.exe -c "import polars; print(polars.__version__)"
|
||
|
||
# 4. 依赖版本
|
||
runtime\python\python.exe -c "import numpy; print(numpy.__version__)"
|
||
runtime\python\python.exe -c "import sklearn; print(sklearn.__version__)"
|
||
```
|
||
|
||
### 10.5 故障诊断流程
|
||
|
||
```
|
||
出现跨机差异
|
||
│
|
||
├─ Step 1: 收集两台机器的 logs/tianxuan.log
|
||
│
|
||
├─ Step 2: 运行 diagnose_compare.py 对比日志
|
||
│ └─ 找到首个 [DIFF]
|
||
│
|
||
├─ Step 3: 判断差异类型
|
||
│ ├─ [LOAD_SCHEMA] 列类型不同
|
||
│ │ └─ 检查 Polars 版本一致、PYTHONUTF8 生效
|
||
│ │
|
||
│ ├─ [FIND_COL] 关键词匹配不同
|
||
│ │ └─ 确认 frozenset→tuple 修复已应用(消除哈希随机性)
|
||
│ │
|
||
│ └─ [CLUSTER_INPUT] 聚类结果不同
|
||
│ └─ 检查 random_state=42、StandardScaler 一致性
|
||
│
|
||
├─ Step 4: 应急修复
|
||
│ ├─ 手动指定实体列:--entity-col src_ip
|
||
│ ├─ 强制编码:set PYTHONUTF8=1
|
||
│ └─ 使用 tuple 替代 frozenset(已在 entity_aggregator.py 中修复)
|
||
│
|
||
└─ Step 5: 归档诊断信息
|
||
( echo === 天璇故障诊断报告 ===
|
||
date /t & time /t
|
||
runtime\python\python.exe --version
|
||
type logs\tianxuan.log
|
||
) > 诊断报告_%COMPUTERNAME%.txt
|
||
```
|
||
|
||
### 10.6 已知跨机问题(已修复)
|
||
|
||
| 问题 | 根因 | 修复 |
|
||
|------|------|------|
|
||
| "mean on str" 错误 | frozenset 哈希随机性 | frozenset → tuple 确定性迭代 |
|
||
| "ZZZ不在{XXX,YYY}中" 错误 | Python 编码不一致 | `PYTHONUTF8=1` 强制 UTF-8 |
|
||
| DB 特征不保存 | Django ORM 在 async 上下文中的限制 | `sync_to_async` 包装 |
|
||
| 图表不显示 | Chart.js 从未下载 | 改用纯 Canvas 2D API |
|
||
|
||
---
|
||
|
||
> **更多技术细节**:请参阅 `docs/故障诊断手册.md` 获取跨机问题深度排查指南。
|