mirror of
https://github.com/ParrotXray/Mantis.git
synced 2026-08-23 16:20:26 +09:00
refactor/rename-mantis (#12)
* refactor: Rename project from NetGuardia to Mantis * fix: convert mantis-frontend from tracked files to submodule * wip * feat: Suricata integration stabilization and config unification Fix a series of bugs in the Suricata daemon integration and unify configuration so users interact only with config.toml. * docs: sur-001-c1 Suricata integration stabilization findings
This commit is contained in:
parent
40d420a0d7
commit
46a69dd49c
@ -1,11 +1,11 @@
|
||||
---
|
||||
name: research
|
||||
description: Interactive research assistant for NetGuardia. You ask questions; I research, reason, and discuss with you.
|
||||
description: Interactive research assistant for Mantis. You ask questions; I research, reason, and discuss with you.
|
||||
---
|
||||
|
||||
# Interactive Research — Ask / Research / Discuss
|
||||
|
||||
You are a research discussion partner for the NetGuardia project.
|
||||
You are a research discussion partner for the Mantis project.
|
||||
|
||||
## Hierarchy
|
||||
|
||||
@ -38,7 +38,7 @@ these priorities and surface that context in your response.
|
||||
|
||||
## How This Works
|
||||
|
||||
1. **User asks a question** — about ML inference, eBPF capture, rule engine, API/frontend, or anything NetGuardia-related.
|
||||
1. **User asks a question** — about ML inference, eBPF capture, rule engine, API/frontend, or anything Mantis-related.
|
||||
2. **Research** — search docs, source code, papers, or reason from existing findings. Check `.research/findings/` for prior context before searching externally.
|
||||
3. **Discuss** — present findings clearly, state confidence level, surface open questions, and invite follow-up.
|
||||
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -12,7 +12,7 @@ target/
|
||||
logs
|
||||
.log
|
||||
.txt
|
||||
net-guardia/static/web
|
||||
mantis/static/web
|
||||
*.mmdb
|
||||
node_modules/
|
||||
.next/
|
||||
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -1,3 +1,3 @@
|
||||
[submodule "net-guardia-frontend"]
|
||||
path = net-guardia-frontend
|
||||
[submodule "mantis-frontend"]
|
||||
path = mantis-frontend
|
||||
url = https://github.com/ParrotXray/NetGuardia-frontend-academic-research.git
|
||||
|
||||
138
.research/findings/tasks/sur-001-c1.md
Normal file
138
.research/findings/tasks/sur-001-c1.md
Normal file
@ -0,0 +1,138 @@
|
||||
# sur-001: Suricata Integration Stabilization and Config Unification
|
||||
**Cycle**: 1 | **Theme**: backend-detection | **Kind**: investigation + design + fix | **Status**: done
|
||||
**Date**: 2026-05-22
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Diagnosed and resolved a series of bugs in the Suricata daemon integration, then refactored
|
||||
the configuration system so users interact with a single `config.toml` instead of maintaining
|
||||
a separate `suricata.yaml`. Config and suppress content are now generated in-memory and
|
||||
delivered to Suricata via `memfd_create`, writing nothing to disk.
|
||||
|
||||
---
|
||||
|
||||
## Findings
|
||||
|
||||
### Q: Why did Suricata fail with MirrorSetupFailed "No such file or directory (os error 2)"?
|
||||
|
||||
A: Two independent causes, both present simultaneously.
|
||||
|
||||
1. `iproute2` was not installed in the container. `Command::new("ip").output()` returns
|
||||
`io::Error(ENOENT)` when the binary is not found, which maps to `MirrorSetupFailed`.
|
||||
|
||||
2. `MIRROR_PEER = "mantis-mirror-peer"` is 18 characters, exceeding the Linux kernel limit
|
||||
of `IFNAMSIZ - 1 = 15`. The kernel rejects the interface name; `ip link add` fails, so
|
||||
`/sys/class/net/mantis-mirror/ifindex` never appears, and `get_ifindex` returns ENOENT.
|
||||
|
||||
**Fix**: Installed `iproute2`; renamed `MIRROR_PEER` to `"mantis-peer"` (11 chars).
|
||||
|
||||
**Confidence**: high — confirmed by testing `ip link add` with both names.
|
||||
|
||||
---
|
||||
|
||||
### Q: Why did Suricata log output not appear when stderr was piped?
|
||||
|
||||
A: Suricata's console logger calls `isatty(2)` at startup. When stderr is not a TTY
|
||||
(i.e., `Stdio::piped()` or `Stdio::null()`), Suricata automatically disables console output —
|
||||
this is documented behavior matching daemon mode. `stdbuf` has no effect because Suricata's
|
||||
log system does not use libc stdio buffering.
|
||||
|
||||
**Fix**: Configure Suricata to write operational logs to `/tmp/suricata.log` via the yaml
|
||||
`logging: file:` section. A dedicated `suricata-log` thread tails the file and forwards lines
|
||||
into Mantis's tracing system, routing by prefix (Error/Warn/Notice/Info).
|
||||
|
||||
**Confidence**: high — confirmed via web search (Suricata forum + OISF docs).
|
||||
|
||||
---
|
||||
|
||||
### Q: Why did Suricata fail to start after switching to generated config via memfd?
|
||||
|
||||
A: The yaml template used Rust's `"\n\` + source newline` continuation syntax, which is
|
||||
designed to strip leading whitespace from the next source line. This silently removed all
|
||||
YAML indentation, producing a structurally invalid document that Suricata rejected.
|
||||
|
||||
**Fix**: Switched template to `r#"..."#` raw string, which preserves whitespace exactly as
|
||||
written in source. No external YAML library required.
|
||||
|
||||
**Confidence**: high — confirmed by inspecting `/tmp/suricata-mantis.yaml` before and after.
|
||||
|
||||
---
|
||||
|
||||
### Q: Did Suricata support /proc/self/fd/N as a config path?
|
||||
|
||||
A: Yes. `memfd_create` without `MFD_CLOEXEC` produces a file descriptor that survives
|
||||
`fork`+`exec` into the Suricata child process. Suricata can open `/proc/self/fd/N` to read
|
||||
the in-memory config. The approach works for both the main config and the suppress/threshold
|
||||
file. The earlier failure was entirely due to malformed YAML, not the memfd mechanism.
|
||||
|
||||
**Confidence**: high — confirmed working after YAML fix.
|
||||
|
||||
---
|
||||
|
||||
### Q: What was wrong with the af-packet interface name in suricata.yaml?
|
||||
|
||||
A: The static `suricata.yaml` still referenced `mantis-mirror-peer` (the old peer name)
|
||||
after the rename to `mantis-peer`. Suricata was listening on a non-existent interface and
|
||||
capturing no traffic. This was silently ignored — Suricata started but processed zero packets.
|
||||
|
||||
**Fix**: Interface name is now derived from the `MIRROR_PEER` constant in `engine.rs` and
|
||||
injected into the generated yaml, making divergence impossible.
|
||||
|
||||
**Confidence**: high.
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### Single config entry point
|
||||
`suricata.yaml` was promoted from a user-edited file to a generated internal artifact.
|
||||
All user-facing Suricata settings live in `config.toml` under `[Config.suricata]`:
|
||||
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `home_net` | `String` | Required. Protected network CIDR. |
|
||||
| `worker_cpu_set` | `Option<[u32; 2]>` | Same semantics as `xsk_cpu_set`. |
|
||||
| `management_cpu` | `Option<u32>` | Management thread CPU pin. |
|
||||
| `af_packet_threads` | `String` | Default `"auto"`. |
|
||||
| `af_packet_ring_size` | `u32` | Default 2048. |
|
||||
| `af_packet_block_size` | `u32` | Default 131072. |
|
||||
| `suppress` | `Vec<String>` | Raw Suricata suppress/threshold lines. |
|
||||
|
||||
Removing `[Config.suricata]` entirely disables the rule engine.
|
||||
|
||||
### suppress as raw strings
|
||||
Rather than defining a structured `SuppressEntry` with parsed fields, suppress entries are
|
||||
stored as raw Suricata syntax strings. This is more flexible (supports `threshold`,
|
||||
`rate_filter`, etc.) and lets users copy directly from Suricata documentation.
|
||||
|
||||
### memfd_create for config delivery
|
||||
Both the generated yaml and suppress content are written to anonymous in-memory files via
|
||||
`memfd_create(0)` (no `MFD_CLOEXEC`), inherited by the Suricata child process, and passed
|
||||
as `/proc/self/fd/N` paths. Parent closes its copies immediately after `spawn()`. Nothing
|
||||
is written to the filesystem.
|
||||
|
||||
---
|
||||
|
||||
## Unexpected Discoveries
|
||||
|
||||
- `suricata.yaml` referenced the old peer interface name (`mantis-mirror-peer`) even after
|
||||
the veth rename, causing Suricata to silently capture zero traffic. The bug was masked
|
||||
because Suricata started without error.
|
||||
- Rust's `"\n\` continuation eats leading whitespace — a non-obvious footgun when building
|
||||
indentation-sensitive file formats inline.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should `af_packet_ring_size` and `af_packet_block_size` be exposed to users, or are the
|
||||
defaults sufficient for the research prototype?
|
||||
|
||||
## Impact on Downstream Tasks
|
||||
|
||||
- **active-response**: XDP blocking now has a working rule engine to corroborate with.
|
||||
Fusion alerts from Suricata + ML are available as the trigger signal.
|
||||
- **tls-analysis**: The `app-layer: tls: enabled: yes` and EVE tls event output can be
|
||||
enabled in the generated yaml without user-visible config changes.
|
||||
@ -1,9 +1,9 @@
|
||||
# NetGuardia Research State
|
||||
# Mantis Research State
|
||||
# Updated: 2026-05-21
|
||||
|
||||
[[epics]]
|
||||
id = "nids-v1"
|
||||
title = "NetGuardia NIDS v1 — Research Prototype"
|
||||
title = "Mantis NIDS v1 — Research Prototype"
|
||||
status = "active"
|
||||
description = """
|
||||
End-to-end NIDS combining eBPF/AF_XDP packet capture, LSTM autoencoder ML inference,
|
||||
|
||||
16
CLAUDE.md
16
CLAUDE.md
@ -1,4 +1,4 @@
|
||||
# NetGuardia
|
||||
# Mantis
|
||||
|
||||
Network intrusion detection system combining eBPF packet capture with ML-based anomaly detection and Suricata/Snort rule matching.
|
||||
|
||||
@ -19,12 +19,12 @@ Network intrusion detection system combining eBPF packet capture with ML-based a
|
||||
## Workspace Structure
|
||||
|
||||
```
|
||||
net-guardia/ - Main application (ML, eBPF userspace, HTTP API, WebSocket)
|
||||
mantis/ - Main application (ML, eBPF userspace, HTTP API, WebSocket)
|
||||
common/ - Shared types used by both userspace and eBPF programs
|
||||
macros/ - Procedural macros: log!, traceable!, loggable!
|
||||
ingress-ebpf/ - eBPF ingress packet capture program
|
||||
egress-ebpf/ - eBPF egress packet capture program
|
||||
net-guardia-frontend/ - Next.js web UI
|
||||
mantis-frontend/ - Next.js web UI
|
||||
```
|
||||
|
||||
## Build
|
||||
@ -33,17 +33,17 @@ Full build requires eBPF toolchain and system libs (libelf, boost for vectorscan
|
||||
|
||||
To type-check without eBPF (for ML/API changes):
|
||||
```bash
|
||||
SKIP_EBPF_BUILD=1 cargo check --package net-guardia
|
||||
SKIP_EBPF_BUILD=1 cargo check --package mantis
|
||||
```
|
||||
|
||||
Ignore these expected errors when SKIP_EBPF_BUILD is set:
|
||||
- `environment variable ARTIFACTCS_PATH not defined`
|
||||
- `environment variable CSV_RECORD_PATH not defined`
|
||||
- `environment variable RULES_DB_PATH not defined`
|
||||
- `couldn't read .../net-guardia-ingress`
|
||||
- `couldn't read .../net-guardia-egress`
|
||||
- `couldn't read .../mantis-ingress`
|
||||
- `couldn't read .../mantis-egress`
|
||||
|
||||
## net-guardia Source Layout
|
||||
## mantis Source Layout
|
||||
|
||||
```
|
||||
src/
|
||||
@ -129,7 +129,7 @@ Session is wrapped in `Mutex<Session>` because `Session::run` requires `&mut sel
|
||||
## Config
|
||||
|
||||
Runtime config: `config.toml`
|
||||
ML artifacts: `net-guardia/static/artifacts/`
|
||||
ML artifacts: `mantis/static/artifacts/`
|
||||
- `deep_autoencoder.onnx` - LSTM autoencoder model
|
||||
- `inference_config.json` - window size, feature names, scaler params, threshold
|
||||
|
||||
|
||||
88
Cargo.lock
generated
88
Cargo.lock
generated
@ -1662,6 +1662,50 @@ dependencies = [
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mantis"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix",
|
||||
"actix-cors",
|
||||
"actix-web",
|
||||
"actix-ws",
|
||||
"aya",
|
||||
"aya-log",
|
||||
"cargo_metadata",
|
||||
"cc",
|
||||
"chrono",
|
||||
"common",
|
||||
"crossbeam",
|
||||
"dotenvy",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"libc",
|
||||
"lru",
|
||||
"macros",
|
||||
"maxminddb",
|
||||
"mime_guess",
|
||||
"ndarray 0.17.2",
|
||||
"network-types",
|
||||
"ort",
|
||||
"ort-tract",
|
||||
"parking_lot",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sysinfo",
|
||||
"thiserror 2.0.16",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
"tract-onnx",
|
||||
"url",
|
||||
"xsk-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
@ -1797,50 +1841,6 @@ dependencies = [
|
||||
"rawpointer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "net-guardia"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix",
|
||||
"actix-cors",
|
||||
"actix-web",
|
||||
"actix-ws",
|
||||
"aya",
|
||||
"aya-log",
|
||||
"cargo_metadata",
|
||||
"cc",
|
||||
"chrono",
|
||||
"common",
|
||||
"crossbeam",
|
||||
"dotenvy",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"libc",
|
||||
"lru",
|
||||
"macros",
|
||||
"maxminddb",
|
||||
"mime_guess",
|
||||
"ndarray 0.17.2",
|
||||
"network-types",
|
||||
"ort",
|
||||
"ort-tract",
|
||||
"parking_lot",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sysinfo",
|
||||
"thiserror 2.0.16",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
"tract-onnx",
|
||||
"url",
|
||||
"xsk-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "network-types"
|
||||
version = "0.1.0"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["net-guardia", "common", "macros", "ingress-ebpf", "egress-ebpf"]
|
||||
default-members = ["net-guardia", "common"]
|
||||
members = ["mantis", "common", "macros", "ingress-ebpf", "egress-ebpf"]
|
||||
default-members = ["mantis", "common"]
|
||||
|
||||
[workspace.dependencies]
|
||||
aya = { version = "0.13.1", default-features = false }
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
# NetGuardia
|
||||
# Mantis
|
||||
|
||||
## Project Overview
|
||||
|
||||
**NetGuardia** is a high-performance network security solution that combines eBPF XDP technology with deep learning models to provide advanced network protection. The system operates as a standalone network appliance that can run on any Ubuntu-based system with compatible network hardware.
|
||||
**Mantis** is a high-performance network security solution that combines eBPF XDP technology with deep learning models to provide advanced network protection. The system operates as a standalone network appliance that can run on any Ubuntu-based system with compatible network hardware.
|
||||
|
||||
## Core Technologies
|
||||
|
||||
@ -50,11 +50,10 @@
|
||||
- Root/sudo access for eBPF program loading
|
||||
|
||||
## Hardware Compatibility
|
||||
NetGuardia is designed to work on any Ubuntu-based system meeting the following requirements:
|
||||
Mantis is designed to work on any Ubuntu-based system meeting the following requirements:
|
||||
|
||||
- Network Interface: Any dual-port NIC supporting XDP native or offload mode (Intel i350 T2 recommended)
|
||||
- CPU: Multi-core processor recommended for optimal performance
|
||||
- Memory: 8GB RAM minimum, 16GB or more for high-traffic environments
|
||||
|
||||
The system is not limited to embedded platforms and can be deployed on standard server hardware, virtual machines, or dedicated appliances running Ubuntu.
|
||||
|
||||
|
||||
6
TODO
6
TODO
@ -1,5 +1,5 @@
|
||||
============================================================
|
||||
NetGuardia TODO (updated 2026-05-21)
|
||||
Mantis TODO (updated 2026-05-21)
|
||||
============================================================
|
||||
|
||||
-- DONE (archived) ----------------------------------------
|
||||
@ -17,7 +17,7 @@ NetGuardia TODO (updated 2026-05-21)
|
||||
Removed: vectorscan, rusqlite, protolens, pcre2, byte_test/jump/extract,
|
||||
app-layer-protocol, threshold, QUIC parser, suppress list (SQLite).
|
||||
Replaced by: suricata daemon + veth mirror + EVE JSON unix socket.
|
||||
Suricata handles all signature matching; NetGuardia reads alerts via output.rs.
|
||||
Suricata handles all signature matching; Mantis reads alerts via output.rs.
|
||||
[x] Suricata EVE socket race condition (bind before spawn)
|
||||
[x] af-packet block-size too small (32768 -> 131072)
|
||||
[x] Suricata noisy rule categories (exclude emerging-info/policy/user_agents)
|
||||
@ -80,4 +80,4 @@ NetGuardia TODO (updated 2026-05-21)
|
||||
[ ] Dashboard attack/protocol counters never reset
|
||||
Accumulate for entire session lifetime; charts show historical totals.
|
||||
Consider: sliding window reset every N minutes, or cap at last 500 alerts.
|
||||
File: net-guardia-frontend/...dashboard.tsx
|
||||
File: mantis-frontend/...dashboard.tsx
|
||||
|
||||
16
config.toml
16
config.toml
@ -40,5 +40,17 @@ ml_cpu = 7
|
||||
|
||||
ae_threshold_method = "94"
|
||||
|
||||
# Suricata daemon mode. Remove or comment out to disable the rule engine.
|
||||
suricata_name = "suricata.yaml"
|
||||
# Suricata rule engine. Remove this entire section to disable.
|
||||
[Config.suricata]
|
||||
home_net = "140.130.34.0/24"
|
||||
worker_cpu_set = [4, 6]
|
||||
management_cpu = 0
|
||||
af_packet_threads = "auto"
|
||||
af_packet_ring_size = 2048
|
||||
af_packet_block_size = 131072
|
||||
|
||||
# Suppress known false positives — paste Suricata suppress lines directly.
|
||||
# suppress = [
|
||||
# "suppress gen_id 1, sig_id 2001234",
|
||||
# "suppress gen_id 1, sig_id 2001234, track by_src, ip 192.168.1.0/24",
|
||||
# ]
|
||||
@ -13,7 +13,7 @@ aya-log-ebpf = { workspace = true }
|
||||
which = "8.0.0"
|
||||
|
||||
[[bin]]
|
||||
name = "net-guardia-egress"
|
||||
name = "mantis-egress"
|
||||
path = "src/main.rs"
|
||||
test = false
|
||||
doctest = false
|
||||
|
||||
@ -20,7 +20,7 @@ static PARSED_PACKET: PerCpuArray<Event> = PerCpuArray::with_max_entries(1, 0);
|
||||
static EGRESS_XSKS_MAP: XskMap = XskMap::pinned(64, 0);
|
||||
|
||||
#[xdp]
|
||||
pub fn net_guardia(ctx: XdpContext) -> u32 {
|
||||
pub fn mantis(ctx: XdpContext) -> u32 {
|
||||
unsafe {
|
||||
let _ = packet_intake(ctx);
|
||||
xdp_action::XDP_PASS
|
||||
|
||||
@ -14,7 +14,7 @@ network-types = { workspace = true }
|
||||
which = "8.0.0"
|
||||
|
||||
[[bin]]
|
||||
name = "net-guardia-ingress"
|
||||
name = "mantis-ingress"
|
||||
path = "src/main.rs"
|
||||
test = false
|
||||
doctest = false
|
||||
|
||||
@ -22,7 +22,7 @@ static PARSED_PACKET: PerCpuArray<Event> = PerCpuArray::with_max_entries(1, 0);
|
||||
static INGRESS_XSKS_MAP: XskMap = XskMap::pinned(64, 0);
|
||||
|
||||
#[xdp]
|
||||
pub fn net_guardia(ctx: XdpContext) -> u32 {
|
||||
pub fn mantis(ctx: XdpContext) -> u32 {
|
||||
unsafe {
|
||||
let _ = packet_intake(&ctx);
|
||||
let _ = PROGRAM_ARRAY.tail_call(&ctx, TRANSMISSION);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "net-guardia"
|
||||
name = "mantis"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
@ -53,5 +53,5 @@ cc = "1"
|
||||
dotenvy = "0.15.7"
|
||||
|
||||
[[bin]]
|
||||
name = "net-guardia"
|
||||
name = "mantis"
|
||||
path = "src/main.rs"
|
||||
@ -5,7 +5,7 @@ use std::path::PathBuf;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use cargo_metadata::{Artifact, CompilerMessage, Message, Metadata, MetadataCommand, Package, Target, TargetKind};
|
||||
use cargo_metadata::{Artifact, CompilerMessage, Message, Metadata, MetadataCommand, Package, Target};
|
||||
|
||||
fn main() {
|
||||
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
@ -22,8 +22,6 @@ fn main() {
|
||||
.unwrap()
|
||||
.join(format!("{}-frontend", project_name));
|
||||
|
||||
// ── All cargo: directives in one place ────────────────────────────────
|
||||
|
||||
println!("cargo:rustc-env=ARTIFACTCS_PATH={}", artifact_dir.display());
|
||||
println!("cargo:rustc-env=CSV_RECORD_PATH={}", csv_dir.display());
|
||||
println!("cargo:rustc-env=ONNXRUNTIME_PATH={}", onnxruntime_dir.display());
|
||||
@ -43,11 +41,9 @@ fn main() {
|
||||
println!("cargo:rerun-if-changed={}", frontend_dir.join(item).display());
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
if env::var_os("SKIP_EBPF_BUILD").is_some() {
|
||||
let out = PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
for name in &["net-guardia-ingress", "net-guardia-egress"] {
|
||||
for name in &["mantis-ingress", "mantis-egress"] {
|
||||
let path = out.join(name);
|
||||
if !path.exists() {
|
||||
fs::write(&path, []).unwrap_or_else(|e| panic!("cannot write stub {path:?}: {e}"));
|
||||
@ -79,104 +75,91 @@ fn build_ingress_ebpf() {
|
||||
panic!("unsupported endian={:?}", endian)
|
||||
};
|
||||
|
||||
let build_ebpf = true;
|
||||
if build_ebpf {
|
||||
let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
let target = format!("{target}-unknown-none");
|
||||
|
||||
let target = format!("{target}-unknown-none");
|
||||
let Package { manifest_path, .. } = ebpf_package;
|
||||
let ebpf_dir = manifest_path.parent().unwrap();
|
||||
|
||||
let Package { manifest_path, .. } = ebpf_package;
|
||||
let ebpf_dir = manifest_path.parent().unwrap();
|
||||
println!("cargo:rerun-if-changed={}", ebpf_dir.as_str());
|
||||
|
||||
println!("cargo:rerun-if-changed={}", ebpf_dir.as_str());
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.args([
|
||||
"build",
|
||||
"-Z",
|
||||
"build-std=core",
|
||||
"--bins",
|
||||
"--message-format=json",
|
||||
"--release",
|
||||
"--target",
|
||||
&target,
|
||||
]);
|
||||
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.args([
|
||||
"build",
|
||||
"-Z",
|
||||
"build-std=core",
|
||||
"--bins",
|
||||
"--message-format=json",
|
||||
"--release",
|
||||
"--target",
|
||||
&target,
|
||||
]);
|
||||
cmd.env("CARGO_CFG_BPF_TARGET_ARCH", arch);
|
||||
|
||||
cmd.env("CARGO_CFG_BPF_TARGET_ARCH", arch);
|
||||
for key in ["RUSTUP_TOOLCHAIN", "RUSTC", "RUSTC_WORKSPACE_WRAPPER"] {
|
||||
cmd.env_remove(key);
|
||||
}
|
||||
cmd.current_dir(ebpf_dir);
|
||||
|
||||
for key in ["RUSTUP_TOOLCHAIN", "RUSTC", "RUSTC_WORKSPACE_WRAPPER"] {
|
||||
cmd.env_remove(key);
|
||||
let ebpf_target_dir = out_dir.join("../ingress-ebpf");
|
||||
cmd.arg("--target-dir").arg(&ebpf_target_dir);
|
||||
|
||||
let mut child = cmd
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
|
||||
let Child { stdout, stderr, .. } = &mut child;
|
||||
|
||||
let stderr = stderr.take().unwrap();
|
||||
let stderr = BufReader::new(stderr);
|
||||
let stderr = std::thread::spawn(move || {
|
||||
for line in stderr.lines() {
|
||||
let line = line.unwrap();
|
||||
println!("{line}");
|
||||
}
|
||||
cmd.current_dir(ebpf_dir);
|
||||
});
|
||||
|
||||
let ebpf_target_dir = out_dir.join("../ingress-ebpf");
|
||||
cmd.arg("--target-dir").arg(&ebpf_target_dir);
|
||||
|
||||
let mut child = cmd
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
|
||||
let Child { stdout, stderr, .. } = &mut child;
|
||||
|
||||
let stderr = stderr.take().unwrap();
|
||||
let stderr = BufReader::new(stderr);
|
||||
let stderr = std::thread::spawn(move || {
|
||||
for line in stderr.lines() {
|
||||
let line = line.unwrap();
|
||||
println!("{line}");
|
||||
let stdout = stdout.take().unwrap();
|
||||
let stdout = BufReader::new(stdout);
|
||||
let mut executables = Vec::new();
|
||||
for message in Message::parse_stream(stdout) {
|
||||
#[allow(clippy::collapsible_match)]
|
||||
match message.expect("valid JSON") {
|
||||
Message::CompilerArtifact(Artifact {
|
||||
executable,
|
||||
target: Target { name, .. },
|
||||
..
|
||||
}) => {
|
||||
if let Some(executable) = executable {
|
||||
executables.push((name, executable.into_std_path_buf()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let stdout = stdout.take().unwrap();
|
||||
let stdout = BufReader::new(stdout);
|
||||
let mut executables = Vec::new();
|
||||
for message in Message::parse_stream(stdout) {
|
||||
#[allow(clippy::collapsible_match)]
|
||||
match message.expect("valid JSON") {
|
||||
Message::CompilerArtifact(Artifact {
|
||||
executable,
|
||||
target: Target { name, .. },
|
||||
..
|
||||
}) => {
|
||||
if let Some(executable) = executable {
|
||||
executables.push((name, executable.into_std_path_buf()));
|
||||
}
|
||||
}
|
||||
Message::CompilerMessage(CompilerMessage { message, .. }) => {
|
||||
for line in message.rendered.unwrap_or_default().split('\n') {
|
||||
println!("{line}");
|
||||
}
|
||||
}
|
||||
Message::TextLine(line) => {
|
||||
Message::CompilerMessage(CompilerMessage { message, .. }) => {
|
||||
for line in message.rendered.unwrap_or_default().split('\n') {
|
||||
println!("{line}");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let status = child
|
||||
.wait()
|
||||
.unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));
|
||||
assert_eq!(status.code(), Some(0), "{cmd:?} failed: {status:?}");
|
||||
|
||||
stderr.join().map_err(std::panic::resume_unwind).unwrap();
|
||||
|
||||
for (name, binary) in executables {
|
||||
let dst = out_dir.join(name);
|
||||
let _: u64 =
|
||||
fs::copy(&binary, &dst).unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}"));
|
||||
}
|
||||
} else {
|
||||
let Package { targets, .. } = ebpf_package;
|
||||
for Target { name, kind, .. } in targets {
|
||||
if *kind != [TargetKind::Bin] {
|
||||
continue;
|
||||
Message::TextLine(line) => {
|
||||
println!("{line}");
|
||||
}
|
||||
let dst = out_dir.join(name);
|
||||
fs::write(&dst, []).unwrap_or_else(|err| panic!("failed to create {dst:?}: {err}"));
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let status = child
|
||||
.wait()
|
||||
.unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));
|
||||
assert_eq!(status.code(), Some(0), "{cmd:?} failed: {status:?}");
|
||||
|
||||
stderr.join().map_err(std::panic::resume_unwind).unwrap();
|
||||
|
||||
for (name, binary) in executables {
|
||||
let dst = out_dir.join(name);
|
||||
let _: u64 =
|
||||
fs::copy(&binary, &dst).unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}"));
|
||||
}
|
||||
}
|
||||
|
||||
fn build_egress_ebpf() {
|
||||
@ -198,105 +181,92 @@ fn build_egress_ebpf() {
|
||||
panic!("unsupported endian={:?}", endian)
|
||||
};
|
||||
|
||||
let build_ebpf = true;
|
||||
if build_ebpf {
|
||||
let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
let target = format!("{target}-unknown-none");
|
||||
|
||||
let target = format!("{target}-unknown-none");
|
||||
let Package { manifest_path, .. } = ebpf_package;
|
||||
let ebpf_dir = manifest_path.parent().unwrap();
|
||||
|
||||
let Package { manifest_path, .. } = ebpf_package;
|
||||
let ebpf_dir = manifest_path.parent().unwrap();
|
||||
println!("cargo:rerun-if-changed={}", ebpf_dir.as_str());
|
||||
|
||||
println!("cargo:rerun-if-changed={}", ebpf_dir.as_str());
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.args([
|
||||
"build",
|
||||
"-Z",
|
||||
"build-std=core",
|
||||
"--bins",
|
||||
"--message-format=json",
|
||||
"--release",
|
||||
"--target",
|
||||
&target,
|
||||
]);
|
||||
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.args([
|
||||
"build",
|
||||
"-Z",
|
||||
"build-std=core",
|
||||
"--bins",
|
||||
"--message-format=json",
|
||||
"--release",
|
||||
"--target",
|
||||
&target,
|
||||
]);
|
||||
cmd.env("CARGO_CFG_BPF_TARGET_ARCH", arch);
|
||||
cmd.env("CARGO_TERM_COLOR", "always");
|
||||
|
||||
cmd.env("CARGO_CFG_BPF_TARGET_ARCH", arch);
|
||||
cmd.env("CARGO_TERM_COLOR", "always");
|
||||
for key in ["RUSTUP_TOOLCHAIN", "RUSTC", "RUSTC_WORKSPACE_WRAPPER"] {
|
||||
cmd.env_remove(key);
|
||||
}
|
||||
cmd.current_dir(ebpf_dir);
|
||||
|
||||
for key in ["RUSTUP_TOOLCHAIN", "RUSTC", "RUSTC_WORKSPACE_WRAPPER"] {
|
||||
cmd.env_remove(key);
|
||||
let ebpf_target_dir = out_dir.join("../egress-ebpf");
|
||||
cmd.arg("--target-dir").arg(&ebpf_target_dir);
|
||||
|
||||
let mut child = cmd
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
|
||||
let Child { stdout, stderr, .. } = &mut child;
|
||||
|
||||
let stderr = stderr.take().unwrap();
|
||||
let stderr = BufReader::new(stderr);
|
||||
let stderr = std::thread::spawn(move || {
|
||||
for line in stderr.lines() {
|
||||
let line = line.unwrap();
|
||||
println!("{line}");
|
||||
}
|
||||
cmd.current_dir(ebpf_dir);
|
||||
});
|
||||
|
||||
let ebpf_target_dir = out_dir.join("../egress-ebpf");
|
||||
cmd.arg("--target-dir").arg(&ebpf_target_dir);
|
||||
|
||||
let mut child = cmd
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
|
||||
let Child { stdout, stderr, .. } = &mut child;
|
||||
|
||||
let stderr = stderr.take().unwrap();
|
||||
let stderr = BufReader::new(stderr);
|
||||
let stderr = std::thread::spawn(move || {
|
||||
for line in stderr.lines() {
|
||||
let line = line.unwrap();
|
||||
println!("{line}");
|
||||
let stdout = stdout.take().unwrap();
|
||||
let stdout = BufReader::new(stdout);
|
||||
let mut executables = Vec::new();
|
||||
for message in Message::parse_stream(stdout) {
|
||||
#[allow(clippy::collapsible_match)]
|
||||
match message.expect("valid JSON") {
|
||||
Message::CompilerArtifact(Artifact {
|
||||
executable,
|
||||
target: Target { name, .. },
|
||||
..
|
||||
}) => {
|
||||
if let Some(executable) = executable {
|
||||
executables.push((name, executable.into_std_path_buf()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let stdout = stdout.take().unwrap();
|
||||
let stdout = BufReader::new(stdout);
|
||||
let mut executables = Vec::new();
|
||||
for message in Message::parse_stream(stdout) {
|
||||
#[allow(clippy::collapsible_match)]
|
||||
match message.expect("valid JSON") {
|
||||
Message::CompilerArtifact(Artifact {
|
||||
executable,
|
||||
target: Target { name, .. },
|
||||
..
|
||||
}) => {
|
||||
if let Some(executable) = executable {
|
||||
executables.push((name, executable.into_std_path_buf()));
|
||||
}
|
||||
}
|
||||
Message::CompilerMessage(CompilerMessage { message, .. }) => {
|
||||
for line in message.rendered.unwrap_or_default().split('\n') {
|
||||
println!("{line}");
|
||||
}
|
||||
}
|
||||
Message::TextLine(line) => {
|
||||
Message::CompilerMessage(CompilerMessage { message, .. }) => {
|
||||
for line in message.rendered.unwrap_or_default().split('\n') {
|
||||
println!("{line}");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let status = child
|
||||
.wait()
|
||||
.unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));
|
||||
assert_eq!(status.code(), Some(0), "{cmd:?} failed: {status:?}");
|
||||
|
||||
stderr.join().map_err(std::panic::resume_unwind).unwrap();
|
||||
|
||||
for (name, binary) in executables {
|
||||
let dst = out_dir.join(name);
|
||||
let _: u64 =
|
||||
fs::copy(&binary, &dst).unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}"));
|
||||
}
|
||||
} else {
|
||||
let Package { targets, .. } = ebpf_package;
|
||||
for Target { name, kind, .. } in targets {
|
||||
if *kind != [TargetKind::Bin] {
|
||||
continue;
|
||||
Message::TextLine(line) => {
|
||||
println!("{line}");
|
||||
}
|
||||
let dst = out_dir.join(name);
|
||||
fs::write(&dst, []).unwrap_or_else(|err| panic!("failed to create {dst:?}: {err}"));
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let status = child
|
||||
.wait()
|
||||
.unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));
|
||||
assert_eq!(status.code(), Some(0), "{cmd:?} failed: {status:?}");
|
||||
|
||||
stderr.join().map_err(std::panic::resume_unwind).unwrap();
|
||||
|
||||
for (name, binary) in executables {
|
||||
let dst = out_dir.join(name);
|
||||
let _: u64 =
|
||||
fs::copy(&binary, &dst).unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}"));
|
||||
}
|
||||
}
|
||||
|
||||
fn build_frontend(frontend_dir: &PathBuf, static_dir: &PathBuf) {
|
||||
@ -433,4 +403,3 @@ fn copy_dir_all(src: &PathBuf, dst: &PathBuf) -> std::io::Result<()> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -51,12 +51,12 @@ impl EbpfServices {
|
||||
pub async fn run(
|
||||
self: Arc<Self>,
|
||||
ml_engine: Arc<Engine>,
|
||||
suricata_engine: Arc<SuricataEngine>,
|
||||
suricata_engine: Option<Arc<SuricataEngine>>,
|
||||
) -> Result<(), Error> {
|
||||
let xsk_manager = self.xsk_manager.clone();
|
||||
let statistics = self.statistics.clone();
|
||||
|
||||
xsk_manager.run(Some(ml_engine), Some(suricata_engine), &self.shutdowns)?;
|
||||
xsk_manager.run(Some(ml_engine), suricata_engine, &self.shutdowns)?;
|
||||
|
||||
let statistics_shutdown = statistics.run().await;
|
||||
self.shutdowns.push(statistics_shutdown);
|
||||
@ -21,7 +21,7 @@ pub struct GeoIpService {
|
||||
|
||||
impl GeoIpService {
|
||||
pub fn new(db_name: &str) -> Result<Self, MaxMindDbError> {
|
||||
let db_path = PathBuf::from("net-guardia/static/geo").join(db_name);
|
||||
let db_path = PathBuf::from("mantis/static/geo").join(db_name);
|
||||
Self::with_cache_size(db_path, 10000)
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// net-guardia/src/core/ebpf/health.rs
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@ -29,7 +28,6 @@ pub struct SystemHealth {
|
||||
broadcast_tx: broadcast::Sender<SystemHealthMetrics>,
|
||||
ingress_interface: String,
|
||||
egress_interface: String,
|
||||
// management_interface: String,
|
||||
}
|
||||
|
||||
|
||||
@ -44,7 +42,6 @@ impl SystemHealth {
|
||||
broadcast_tx,
|
||||
ingress_interface: config.ingress_ifname.clone(),
|
||||
egress_interface: config.egress_ifname.clone(),
|
||||
// management_interface: config.management_ifindex.clone(),
|
||||
};
|
||||
|
||||
Ok(health)
|
||||
@ -88,7 +85,6 @@ impl SystemHealth {
|
||||
&components,
|
||||
&self.ingress_interface,
|
||||
&self.egress_interface,
|
||||
// &self.management_interface,
|
||||
);
|
||||
|
||||
drop(system);
|
||||
@ -108,7 +104,6 @@ impl SystemHealth {
|
||||
components: &Components,
|
||||
ingress_interface: &str,
|
||||
egress_interface: &str,
|
||||
// management_interface: &str,
|
||||
) -> SystemHealthMetrics {
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
@ -134,7 +129,6 @@ impl SystemHealth {
|
||||
networks,
|
||||
ingress_interface,
|
||||
egress_interface,
|
||||
// management_interface,
|
||||
);
|
||||
|
||||
let load_average = System::load_average();
|
||||
@ -218,7 +212,6 @@ impl SystemHealth {
|
||||
networks: &Networks,
|
||||
ingress_interface: &str,
|
||||
egress_interface: &str,
|
||||
// management_interface: &str,
|
||||
) -> ConfiguredNetworkStats {
|
||||
let create_network_stats = |interface_name: &str| -> Option<NetworkStats> {
|
||||
networks.get(interface_name).map(|network| NetworkStats {
|
||||
@ -234,7 +227,6 @@ impl SystemHealth {
|
||||
|
||||
let ingress = create_network_stats(ingress_interface);
|
||||
let egress = create_network_stats(egress_interface);
|
||||
// let management = create_network_stats(management_interface);
|
||||
|
||||
if ingress.is_none() {
|
||||
log!(Health::InterfaceNotFound("Ingress".to_string(), ingress_interface.to_string()));
|
||||
@ -242,14 +234,10 @@ impl SystemHealth {
|
||||
if egress.is_none() {
|
||||
log!(Health::InterfaceNotFound("Egress".to_string(), egress_interface.to_string()));
|
||||
}
|
||||
// if management.is_none() {
|
||||
// warn!("Management interface '{}' not found", management_interface);
|
||||
// }
|
||||
|
||||
ConfiguredNetworkStats {
|
||||
ingress,
|
||||
egress,
|
||||
// management,
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,7 +256,6 @@ impl SystemHealth {
|
||||
&components,
|
||||
&self.ingress_interface,
|
||||
&self.egress_interface,
|
||||
// &self.management_interface,
|
||||
)
|
||||
}
|
||||
|
||||
@ -330,11 +317,6 @@ impl SystemHealth {
|
||||
status.overall_healthy = false;
|
||||
status.issues.push("Egress interface not available".to_string());
|
||||
}
|
||||
// if metrics.network_stats.management.is_none() {
|
||||
// status
|
||||
// .warnings
|
||||
// .push("Management interface not available".to_string());
|
||||
// }
|
||||
|
||||
status
|
||||
}
|
||||
@ -33,7 +33,7 @@ pub struct AppServices {
|
||||
pub fusion_engine: Arc<FusionEngine>,
|
||||
pub ml_models: Arc<MLModels>,
|
||||
pub ml_engine: Arc<Engine>,
|
||||
pub suricata_engine: Arc<SuricataEngine>,
|
||||
pub suricata_engine: Option<Arc<SuricataEngine>>,
|
||||
shutdowns: SegQueue<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
@ -77,14 +77,13 @@ impl AppServices {
|
||||
app_config.ml_cpu,
|
||||
));
|
||||
|
||||
let rule_path = PathBuf::from(env!("RULE_PATH")).join(&app_config.suricata_name);
|
||||
let eve_socket = PathBuf::from(env!("RULE_EVE_PATH"));
|
||||
|
||||
let suricata_engine = SuricataEngine::start(
|
||||
rule_path,
|
||||
eve_socket,
|
||||
fusion_engine.clone(),
|
||||
)?;
|
||||
let suricata_engine = if let Some(ref sc) = app_config.suricata {
|
||||
let rule_path = PathBuf::from(env!("RULE_PATH"));
|
||||
let eve_socket = PathBuf::from(env!("RULE_EVE_PATH"));
|
||||
Some(SuricataEngine::start(sc, &rule_path, &eve_socket, fusion_engine.clone())?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
health: Arc::new(health),
|
||||
@ -1,3 +1,3 @@
|
||||
pub mod ebpf;
|
||||
pub mod infrastructure;
|
||||
pub mod system;
|
||||
pub mod ebpf;
|
||||
pub mod infrastructure;
|
||||
pub mod system;
|
||||
@ -112,13 +112,13 @@ impl System {
|
||||
Self::set_memory_limit()?;
|
||||
let ingress_xdp: &mut Xdp = self
|
||||
.ingress_ebpf
|
||||
.program_mut("net_guardia")
|
||||
.program_mut("mantis")
|
||||
.ok_or(EbpfError::ProgramNotFound)?
|
||||
.try_into()
|
||||
.map_err(EbpfError::GetProgramFailed)?;
|
||||
let egress_xdp: &mut Xdp = self
|
||||
.egress_ebpf
|
||||
.program_mut("net_guardia")
|
||||
.program_mut("mantis")
|
||||
.ok_or(EbpfError::ProgramNotFound)?
|
||||
.try_into()
|
||||
.map_err(EbpfError::GetProgramFailed)?;
|
||||
@ -174,7 +174,7 @@ impl System {
|
||||
fn get_ingress_ebpf() -> Result<(Ebpf, ProgramArray<MapData>), Error> {
|
||||
let mut ingress_ebpf = Ebpf::load(aya::include_bytes_aligned!(concat!(
|
||||
env!("OUT_DIR"),
|
||||
"/net-guardia-ingress"
|
||||
"/mantis-ingress"
|
||||
)))
|
||||
.map_err(EbpfError::EbpfNotFound)?;
|
||||
let program_array = ingress_ebpf.take_map("PROGRAM_ARRAY").ok_or(EbpfError::MapNotFound)?;
|
||||
@ -199,7 +199,7 @@ impl System {
|
||||
fn get_egress_ebpf() -> Result<(Ebpf, ProgramArray<MapData>), Error> {
|
||||
let mut egress_ebpf = Ebpf::load(aya::include_bytes_aligned!(concat!(
|
||||
env!("OUT_DIR"),
|
||||
"/net-guardia-egress"
|
||||
"/mantis-egress"
|
||||
)))
|
||||
.map_err(EbpfError::EbpfNotFound)?;
|
||||
let program_array = egress_ebpf.take_map("PROGRAM_ARRAY").ok_or(EbpfError::MapNotFound)?;
|
||||
@ -48,10 +48,6 @@ impl AttackAggregator {
|
||||
false
|
||||
}
|
||||
|
||||
/// L2 aggregation: track anomalous events per src_ip regardless of src_port.
|
||||
/// Returns "FLOOD" when total anomalous flows exceed the threshold, or "SCAN"
|
||||
/// when the number of distinct dst_ports exceeds the scan threshold.
|
||||
/// A per-src_ip cooldown equal to the window duration prevents alert storms.
|
||||
pub fn should_alert_src_ip(&mut self, src_ip: &str, dst_port: u16) -> Option<&'static str> {
|
||||
let now = Instant::now();
|
||||
let window = self.window_duration;
|
||||
@ -222,8 +222,6 @@ impl FlowData {
|
||||
}
|
||||
}
|
||||
|
||||
/// 每個 thread 獨立擁有,不共享,無鎖。
|
||||
/// RSS 保證同一條 flow 永遠落在同一個 queue。
|
||||
pub struct FlowTracker {
|
||||
flows: HashMap<FlowKey, FlowData>,
|
||||
max_flows: usize,
|
||||
@ -308,8 +306,6 @@ impl FlowTracker {
|
||||
.map(|d| d.as_micros() as u64)
|
||||
.unwrap_or(0);
|
||||
self.flows.retain(|_, flow| {
|
||||
// Remove flows that have completed TCP teardown (both FIN or RST)
|
||||
// or have exceeded the idle timeout
|
||||
!flow.is_finished()
|
||||
&& now.saturating_sub(flow.last_time_us) < max_age_us
|
||||
});
|
||||
393
mantis/src/detection/suricata/engine.rs
Normal file
393
mantis/src/detection/suricata/engine.rs
Normal file
@ -0,0 +1,393 @@
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::mem;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use crossbeam::channel::{bounded, Sender};
|
||||
use macros::log;
|
||||
|
||||
use crate::detection::fusion::FusionEngine;
|
||||
use crate::model::config::SuricataConfig;
|
||||
use crate::model::error::suricata::SuricataError;
|
||||
use crate::model::log::suricata::SuricataLog;
|
||||
|
||||
use super::output;
|
||||
|
||||
const MIRROR_IFACE: &str = "mantis-mirror";
|
||||
const MIRROR_PEER: &str = "mantis-peer";
|
||||
const SURICATA_LOG: &str = "/tmp/suricata.log";
|
||||
|
||||
const CHANNEL_CAP: usize = 4096;
|
||||
|
||||
pub struct SuricataEngine {
|
||||
tx: Sender<Vec<u8>>,
|
||||
child: std::sync::Mutex<Child>,
|
||||
}
|
||||
|
||||
impl SuricataEngine {
|
||||
pub fn start(
|
||||
config: &SuricataConfig,
|
||||
rule_path: &Path,
|
||||
eve_socket: &Path,
|
||||
fusion: Arc<FusionEngine>,
|
||||
) -> Result<Arc<Self>, SuricataError> {
|
||||
Self::setup_veth()?;
|
||||
|
||||
let ifindex = Self::get_ifindex(MIRROR_IFACE)?;
|
||||
|
||||
if let Some(path) = eve_socket.to_str() {
|
||||
output::start_eve_reader(path, fusion);
|
||||
}
|
||||
|
||||
let rule_path_str = rule_path.to_str()
|
||||
.ok_or_else(|| SuricataError::InvalidPath { path: rule_path.display().to_string() })?;
|
||||
let eve_socket_str = eve_socket.to_str()
|
||||
.ok_or_else(|| SuricataError::InvalidPath { path: eve_socket.display().to_string() })?;
|
||||
|
||||
let suppress = Self::generate_suppress(&config.suppress);
|
||||
let suppress_fd = Self::yaml_to_memfd(&suppress)?;
|
||||
let suppress_path = format!("/proc/self/fd/{}", suppress_fd);
|
||||
|
||||
let yaml = Self::generate_yaml(config, rule_path_str, eve_socket_str, &suppress_path);
|
||||
let config_fd = Self::yaml_to_memfd(&yaml)?;
|
||||
let config_path = format!("/proc/self/fd/{}", config_fd);
|
||||
|
||||
let _ = std::fs::remove_file(SURICATA_LOG);
|
||||
|
||||
let child = Command::new("suricata")
|
||||
.args(["-c", &config_path, "-i", MIRROR_PEER, "--runmode=workers"])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.map_err(|e| SuricataError::ProcessSpawnFailed { reason: e.to_string() })?;
|
||||
|
||||
// Child has inherited both fds; close our copies.
|
||||
unsafe { libc::close(suppress_fd) };
|
||||
unsafe { libc::close(config_fd) };
|
||||
|
||||
thread::Builder::new()
|
||||
.name("suricata-log".into())
|
||||
.spawn(|| {
|
||||
let log_path = Path::new(SURICATA_LOG);
|
||||
for _ in 0..100 {
|
||||
if log_path.exists() { break; }
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
let file = match std::fs::File::open(log_path) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let inotify_fd = unsafe { libc::inotify_init1(libc::IN_CLOEXEC) };
|
||||
if inotify_fd >= 0 {
|
||||
let path_cstr = std::ffi::CString::new(SURICATA_LOG).unwrap();
|
||||
unsafe { libc::inotify_add_watch(inotify_fd, path_cstr.as_ptr(), libc::IN_MODIFY) };
|
||||
}
|
||||
|
||||
let mut reader = BufReader::new(file);
|
||||
let mut line = String::new();
|
||||
loop {
|
||||
line.clear();
|
||||
match reader.read_line(&mut line) {
|
||||
Ok(0) => {
|
||||
if inotify_fd >= 0 {
|
||||
// Block until Suricata writes more data.
|
||||
let mut buf = [0u8; 64];
|
||||
let n = unsafe {
|
||||
libc::read(inotify_fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len())
|
||||
};
|
||||
if n <= 0 { break; }
|
||||
} else {
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
let trimmed = line.trim_end().to_string();
|
||||
if trimmed.is_empty() { continue; }
|
||||
let lower = trimmed.to_ascii_lowercase();
|
||||
if lower.starts_with("error") || lower.starts_with("critical") {
|
||||
log!(SuricataLog::ProcessError { line: trimmed });
|
||||
} else if lower.starts_with("warn") || lower.starts_with("notice") {
|
||||
log!(SuricataLog::ProcessWarn { line: trimmed });
|
||||
} else {
|
||||
log!(SuricataLog::ProcessInfo { line: trimmed });
|
||||
}
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
if inotify_fd >= 0 {
|
||||
unsafe { libc::close(inotify_fd) };
|
||||
}
|
||||
})
|
||||
.map_err(|e| SuricataError::ProcessSpawnFailed { reason: e.to_string() })?;
|
||||
|
||||
let (tx, rx) = bounded::<Vec<u8>>(CHANNEL_CAP);
|
||||
|
||||
thread::Builder::new()
|
||||
.name("suricata-mirror".into())
|
||||
.spawn(move || {
|
||||
let fd = match Self::open_raw_socket() {
|
||||
Ok(fd) => fd,
|
||||
Err(e) => {
|
||||
log!(e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut sll: libc::sockaddr_ll = unsafe { mem::zeroed() };
|
||||
sll.sll_family = libc::AF_PACKET as u16;
|
||||
sll.sll_protocol = (libc::ETH_P_ALL as u16).to_be();
|
||||
sll.sll_ifindex = ifindex as i32;
|
||||
|
||||
log!(SuricataLog::MirrorReady { iface: MIRROR_IFACE.into() });
|
||||
|
||||
while let Ok(data) = rx.recv() {
|
||||
unsafe {
|
||||
libc::sendto(
|
||||
fd,
|
||||
data.as_ptr() as *const libc::c_void,
|
||||
data.len(),
|
||||
0,
|
||||
&sll as *const libc::sockaddr_ll as *const libc::sockaddr,
|
||||
mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe { libc::close(fd) };
|
||||
log!(SuricataLog::MirrorStopped);
|
||||
})
|
||||
.map_err(|e| SuricataError::ProcessSpawnFailed { reason: e.to_string() })?;
|
||||
|
||||
log!(SuricataLog::Initialized);
|
||||
|
||||
Ok(Arc::new(Self { tx, child: std::sync::Mutex::new(child) }))
|
||||
}
|
||||
|
||||
/* Non-blocking: drops silently when the channel is full under load. */
|
||||
pub fn inject(&self, data: Vec<u8>) {
|
||||
match self.tx.try_send(data) {
|
||||
Ok(()) => {}
|
||||
Err(crossbeam::channel::TrySendError::Full(_)) => {
|
||||
log!(SuricataLog::ChannelFull);
|
||||
}
|
||||
Err(crossbeam::channel::TrySendError::Disconnected(_)) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_suppress(entries: &[String]) -> String {
|
||||
entries.join("\n") + "\n"
|
||||
}
|
||||
|
||||
fn generate_yaml(config: &SuricataConfig, rule_path: &str, eve_socket: &str, suppress_path: &str) -> String {
|
||||
let threading = match (config.worker_cpu_set, config.management_cpu) {
|
||||
(None, None) => r#"threading:
|
||||
set-cpu-affinity: no"#.to_string(),
|
||||
(worker, mgmt) => {
|
||||
let mgmt_cpu = mgmt.unwrap_or(0);
|
||||
let worker_block = match worker {
|
||||
Some([start, end]) => format!(r#" - worker-cpu-set:
|
||||
cpu: [ "{start}-{end}" ]
|
||||
mode: "balanced""#),
|
||||
None => String::new(),
|
||||
};
|
||||
format!(r#"threading:
|
||||
set-cpu-affinity: yes
|
||||
cpu-affinity:
|
||||
- management-cpu-set:
|
||||
cpu: [ {mgmt_cpu} ]
|
||||
{worker_block}"#)
|
||||
}
|
||||
};
|
||||
|
||||
format!(r#"%YAML 1.1
|
||||
---
|
||||
vars:
|
||||
address-groups:
|
||||
HOME_NET: "[{home_net}]"
|
||||
EXTERNAL_NET: "!$HOME_NET"
|
||||
HTTP_SERVERS: "$HOME_NET"
|
||||
SMTP_SERVERS: "$HOME_NET"
|
||||
SQL_SERVERS: "$HOME_NET"
|
||||
DNS_SERVERS: "$HOME_NET"
|
||||
TELNET_SERVERS: "$HOME_NET"
|
||||
AIM_SERVERS: "$EXTERNAL_NET"
|
||||
DC_SERVERS: "$HOME_NET"
|
||||
DNP3_SERVER: "$HOME_NET"
|
||||
DNP3_CLIENT: "$HOME_NET"
|
||||
MODBUS_CLIENT: "$HOME_NET"
|
||||
MODBUS_SERVER: "$HOME_NET"
|
||||
ENIP_CLIENT: "$HOME_NET"
|
||||
ENIP_SERVER: "$HOME_NET"
|
||||
port-groups:
|
||||
HTTP_PORTS: "80"
|
||||
SHELLCODE_PORTS: "!80"
|
||||
ORACLE_PORTS: 1521
|
||||
SSH_PORTS: 22
|
||||
DNP3_PORTS: 20000
|
||||
MODBUS_PORTS: 502
|
||||
FILE_DATA_PORTS: "[$HTTP_PORTS,110,143]"
|
||||
FTP_PORTS: 21
|
||||
VXLAN_PORTS: 4789
|
||||
TEREDO_PORTS: 3544
|
||||
|
||||
default-rule-path: {rule_path}
|
||||
|
||||
rule-files:
|
||||
- "*.rules"
|
||||
|
||||
threshold-file: {suppress_path}
|
||||
|
||||
logging:
|
||||
default-log-level: notice
|
||||
outputs:
|
||||
- console:
|
||||
enabled: no
|
||||
- file:
|
||||
enabled: yes
|
||||
level: info
|
||||
filename: {log_path}
|
||||
|
||||
outputs:
|
||||
- eve-log:
|
||||
enabled: yes
|
||||
filetype: unix_stream
|
||||
filename: {eve_socket}
|
||||
types:
|
||||
- alert:
|
||||
payload: no
|
||||
packet: no
|
||||
metadata: no
|
||||
http-body: no
|
||||
tagged-packets: no
|
||||
- fast:
|
||||
enabled: no
|
||||
- stats:
|
||||
enabled: no
|
||||
|
||||
app-layer:
|
||||
protocols:
|
||||
tls:
|
||||
enabled: yes
|
||||
http:
|
||||
enabled: yes
|
||||
dns:
|
||||
enabled: yes
|
||||
smtp:
|
||||
enabled: yes
|
||||
ssh:
|
||||
enabled: yes
|
||||
|
||||
af-packet:
|
||||
- interface: {iface}
|
||||
threads: {threads}
|
||||
use-mmap: yes
|
||||
tpacket-v3: yes
|
||||
ring-size: {ring_size}
|
||||
block-size: {block_size}
|
||||
|
||||
{threading}
|
||||
|
||||
legacy:
|
||||
uricontent: enabled
|
||||
|
||||
host-mode: sniffer-only
|
||||
"#,
|
||||
home_net = config.home_net,
|
||||
rule_path = rule_path,
|
||||
eve_socket = eve_socket,
|
||||
suppress_path = suppress_path,
|
||||
log_path = SURICATA_LOG,
|
||||
iface = MIRROR_PEER,
|
||||
threads = config.af_packet_threads,
|
||||
ring_size = config.af_packet_ring_size,
|
||||
block_size = config.af_packet_block_size,
|
||||
threading = threading,
|
||||
)
|
||||
}
|
||||
|
||||
fn yaml_to_memfd(yaml: &str) -> Result<i32, SuricataError> {
|
||||
let fd = unsafe {
|
||||
libc::memfd_create(b"suricata-config\0".as_ptr() as *const libc::c_char, 0)
|
||||
};
|
||||
if fd < 0 {
|
||||
let errno = unsafe { *libc::__errno_location() };
|
||||
return Err(SuricataError::MirrorSetupFailed {
|
||||
reason: format!("memfd_create: errno {errno}"),
|
||||
});
|
||||
}
|
||||
let bytes = yaml.as_bytes();
|
||||
let written = unsafe {
|
||||
libc::write(fd, bytes.as_ptr() as *const libc::c_void, bytes.len())
|
||||
};
|
||||
if written < 0 {
|
||||
unsafe { libc::close(fd) };
|
||||
let errno = unsafe { *libc::__errno_location() };
|
||||
return Err(SuricataError::MirrorSetupFailed {
|
||||
reason: format!("memfd write: errno {errno}"),
|
||||
});
|
||||
}
|
||||
unsafe { libc::lseek(fd, 0, libc::SEEK_SET) };
|
||||
Ok(fd)
|
||||
}
|
||||
|
||||
fn setup_veth() -> Result<(), SuricataError> {
|
||||
let _ = Command::new("ip").args(["link", "del", MIRROR_IFACE]).output();
|
||||
|
||||
Command::new("ip")
|
||||
.args(["link", "add", MIRROR_IFACE, "type", "veth", "peer", "name", MIRROR_PEER])
|
||||
.output()
|
||||
.map_err(|e| SuricataError::MirrorSetupFailed { reason: e.to_string() })?;
|
||||
|
||||
for iface in [MIRROR_IFACE, MIRROR_PEER] {
|
||||
Command::new("ip")
|
||||
.args(["link", "set", iface, "up"])
|
||||
.output()
|
||||
.map_err(|e| SuricataError::MirrorSetupFailed { reason: e.to_string() })?;
|
||||
}
|
||||
|
||||
log!(SuricataLog::VethCreated { iface: MIRROR_IFACE.into(), peer: MIRROR_PEER.into() });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_ifindex(name: &str) -> Result<u32, SuricataError> {
|
||||
std::fs::read_to_string(format!("/sys/class/net/{}/ifindex", name))
|
||||
.map_err(|e| SuricataError::MirrorSetupFailed { reason: e.to_string() })?
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(|e: std::num::ParseIntError| SuricataError::MirrorSetupFailed {
|
||||
reason: format!("ifindex parse: {e}"),
|
||||
})
|
||||
}
|
||||
|
||||
fn open_raw_socket() -> Result<i32, SuricataError> {
|
||||
let fd = unsafe {
|
||||
libc::socket(
|
||||
libc::AF_PACKET,
|
||||
libc::SOCK_RAW,
|
||||
(libc::ETH_P_ALL as u16).to_be() as i32,
|
||||
)
|
||||
};
|
||||
if fd < 0 {
|
||||
let errno = unsafe { *libc::__errno_location() };
|
||||
return Err(SuricataError::MirrorSetupFailed {
|
||||
reason: format!("socket(AF_PACKET): errno {errno}"),
|
||||
});
|
||||
}
|
||||
Ok(fd)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SuricataEngine {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(mut child) = self.child.lock() {
|
||||
let _ = child.kill();
|
||||
}
|
||||
let _ = Command::new("ip").args(["link", "del", MIRROR_IFACE]).output();
|
||||
}
|
||||
}
|
||||
@ -11,8 +11,6 @@ use crate::detection::fusion::FusionEngine;
|
||||
use crate::model::log::suricata::SuricataLog;
|
||||
use crate::model::rule_detection::RuleMatch;
|
||||
|
||||
/* ── EVE JSON structs (alert subset) ─────────────────────────────────────── */
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct EveEvent {
|
||||
event_type: String,
|
||||
@ -30,8 +28,6 @@ struct EveAlert {
|
||||
signature: String,
|
||||
}
|
||||
|
||||
/* ── Unix socket EVE reader ──────────────────────────────────────────────── */
|
||||
|
||||
pub fn start_eve_reader(socket_path: &str, fusion: Arc<FusionEngine>) {
|
||||
let path = socket_path.to_owned();
|
||||
|
||||
@ -6,6 +6,25 @@ pub struct ConfigTable {
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct SuricataConfig {
|
||||
pub home_net: String,
|
||||
pub worker_cpu_set: Option<[u32; 2]>,
|
||||
pub management_cpu: Option<u32>,
|
||||
#[serde(default = "default_af_threads")]
|
||||
pub af_packet_threads: String,
|
||||
#[serde(default = "default_af_ring_size")]
|
||||
pub af_packet_ring_size: u32,
|
||||
#[serde(default = "default_af_block_size")]
|
||||
pub af_packet_block_size: u32,
|
||||
#[serde(default)]
|
||||
pub suppress: Vec<String>,
|
||||
}
|
||||
|
||||
fn default_af_threads() -> String { "auto".to_string() }
|
||||
fn default_af_ring_size() -> u32 { 2048 }
|
||||
fn default_af_block_size() -> u32 { 131072 }
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub ingress_ifname: String,
|
||||
@ -30,35 +49,18 @@ pub struct Config {
|
||||
pub flow_timeout_us: u64,
|
||||
pub traffic_logging_mode: bool,
|
||||
pub traffic_log_csv_path: String,
|
||||
/// Optional path to an NSS key log file (SSLKEYLOGFILE) for TLS decryption.
|
||||
/// Only useful in [external]->NetGuardia->[internal] deployments where the
|
||||
/// internal server can be configured to write TLS session keys.
|
||||
pub tls_keylog_path: Option<String>,
|
||||
/// CPU core range [start, end] (inclusive) for XSK packet threads.
|
||||
/// Threads are distributed round-robin: core = start + (queue_id % (end - start + 1)).
|
||||
/// Example: [0, 3] with 8 queues spreads 16 threads across cores 0-3 (4 threads each).
|
||||
/// If absent, no affinity is set.
|
||||
pub xsk_cpu_set: Option<[u32; 2]>,
|
||||
/// CPU core pinned to the ML inference spawn_blocking thread.
|
||||
/// If absent, defaults to the last available core.
|
||||
pub ml_cpu: Option<u32>,
|
||||
/// Alert fusion mode: "or" (alert when either source fires) or "and" (require both).
|
||||
/// Defaults to "or" when absent.
|
||||
#[serde(default = "default_fusion_mode")]
|
||||
pub fusion_mode: String,
|
||||
/// Seconds within which both ML and Rule must fire to be correlated as Fusion.
|
||||
/// Only used in "or" (corroboration window) and "and" modes. Defaults to 10.
|
||||
#[serde(default = "default_fusion_window_secs")]
|
||||
pub fusion_window_secs: u64,
|
||||
/// Key into ae_thresholds in inference_config.json that selects the active
|
||||
/// anomaly detection threshold. Valid values: "90".."99", "mean+2std",
|
||||
/// "mean+1std", "Q3+1.5IQR", "Q3+3.0IQR". Defaults to "95" when absent.
|
||||
#[serde(default = "default_ae_threshold_method")]
|
||||
pub ae_threshold_method: String,
|
||||
|
||||
/// Path to suricata.yaml. If absent, Suricata rule engine is disabled.
|
||||
pub suricata_name: String,
|
||||
|
||||
/// Suricata rule engine config. If absent, the rule engine is disabled.
|
||||
pub suricata: Option<SuricataConfig>,
|
||||
}
|
||||
|
||||
fn default_fusion_mode() -> String {
|
||||
@ -71,4 +73,4 @@ fn default_fusion_window_secs() -> u64 {
|
||||
|
||||
fn default_ae_threshold_method() -> String {
|
||||
"95".to_string()
|
||||
}
|
||||
}
|
||||
@ -23,5 +23,14 @@ loggable! {
|
||||
|
||||
#[error("Suricata mirror channel full — packet dropped")]
|
||||
ChannelFull => tracing::Level::WARN,
|
||||
|
||||
#[error("[suricata] {line}")]
|
||||
ProcessInfo { line: String } => tracing::Level::INFO,
|
||||
|
||||
#[error("[suricata] {line}")]
|
||||
ProcessWarn { line: String } => tracing::Level::WARN,
|
||||
|
||||
#[error("[suricata] {line}")]
|
||||
ProcessError { line: String } => tracing::Level::ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ impl Logging {
|
||||
fs::create_dir_all(log_directory)
|
||||
.map_err(|err| IOError::CreateDirectoryFailed(log_directory, err))?;
|
||||
|
||||
let file_appender = RollingFileAppender::new(Rotation::DAILY, log_directory, "NetGuardia");
|
||||
let file_appender = RollingFileAppender::new(Rotation::DAILY, log_directory, "Mantis");
|
||||
|
||||
let stdout_layer = tracing_subscriber::fmt::layer()
|
||||
.with_file(true)
|
||||
422
mantis/static/rules/compromised-ips.txt
Normal file
422
mantis/static/rules/compromised-ips.txt
Normal file
@ -0,0 +1,422 @@
|
||||
101.96.230.94
|
||||
102.210.82.20
|
||||
103.147.14.125
|
||||
103.210.22.17
|
||||
103.54.101.248
|
||||
104.155.110.106
|
||||
104.155.27.113
|
||||
104.155.78.140
|
||||
104.199.19.60
|
||||
104.199.5.111
|
||||
104.199.85.216
|
||||
104.248.60.91
|
||||
104.252.175.235
|
||||
104.36.21.137
|
||||
106.13.209.152
|
||||
107.174.1.138
|
||||
107.189.24.162
|
||||
110.43.37.72
|
||||
111.170.34.11
|
||||
112.203.68.87
|
||||
115.190.167.144
|
||||
116.110.144.105
|
||||
116.110.145.122
|
||||
116.110.145.99
|
||||
116.110.147.23
|
||||
116.110.152.206
|
||||
116.110.157.92
|
||||
116.110.158.50
|
||||
116.110.19.247
|
||||
116.110.208.21
|
||||
116.110.210.253
|
||||
116.110.21.114
|
||||
116.110.211.187
|
||||
116.110.215.174
|
||||
116.110.217.125
|
||||
116.110.219.1
|
||||
116.110.223.85
|
||||
116.110.2.39
|
||||
116.110.4.188
|
||||
116.110.4.244
|
||||
116.99.168.218
|
||||
116.99.171.134
|
||||
116.99.172.175
|
||||
116.99.172.29
|
||||
116.99.173.71
|
||||
116.99.174.114
|
||||
117.36.231.242
|
||||
118.145.243.156
|
||||
118.196.2.158
|
||||
121.125.67.137
|
||||
121.165.84.80
|
||||
124.123.125.62
|
||||
125.20.210.182
|
||||
129.146.128.34
|
||||
129.153.121.56
|
||||
129.159.149.21
|
||||
130.12.180.27
|
||||
130.211.54.242
|
||||
130.211.57.13
|
||||
131.161.204.66
|
||||
131.186.50.157
|
||||
134.122.110.16
|
||||
134.209.239.4
|
||||
13.52.240.248
|
||||
138.2.102.66
|
||||
138.68.40.82
|
||||
139.59.66.69
|
||||
14.103.78.102
|
||||
143.198.65.165
|
||||
144.126.239.128
|
||||
144.2.91.96
|
||||
144.31.152.46
|
||||
144.31.220.38
|
||||
144.48.8.86
|
||||
147.189.161.77
|
||||
148.135.94.99
|
||||
148.251.195.206
|
||||
150.109.254.65
|
||||
151.115.164.231
|
||||
151.115.79.140
|
||||
152.32.162.42
|
||||
152.53.195.231
|
||||
154.210.208.250
|
||||
158.173.67.12
|
||||
159.203.120.106
|
||||
159.223.196.179
|
||||
159.89.2.106
|
||||
160.119.249.227
|
||||
160.119.76.45
|
||||
160.191.89.7
|
||||
161.132.38.88
|
||||
161.35.98.97
|
||||
161.97.66.49
|
||||
162.243.102.84
|
||||
163.7.9.84
|
||||
164.164.197.148
|
||||
164.90.175.206
|
||||
165.154.224.129
|
||||
165.154.52.159
|
||||
165.232.191.119
|
||||
165.245.180.161
|
||||
167.172.64.25
|
||||
167.99.84.148
|
||||
168.144.79.32
|
||||
168.144.88.210
|
||||
171.231.176.146
|
||||
171.231.178.49
|
||||
171.231.180.149
|
||||
171.231.182.74
|
||||
171.231.186.125
|
||||
171.231.186.56
|
||||
171.231.192.123
|
||||
171.231.192.199
|
||||
171.231.192.222
|
||||
171.231.194.32
|
||||
171.231.196.77
|
||||
171.231.197.49
|
||||
171.231.197.57
|
||||
171.231.198.171
|
||||
171.231.199.119
|
||||
171.231.199.134
|
||||
171.231.199.189
|
||||
171.243.148.18
|
||||
171.243.149.139
|
||||
171.243.149.96
|
||||
171.243.150.164
|
||||
171.243.150.172
|
||||
171.243.150.236
|
||||
171.243.151.49
|
||||
178.104.220.57
|
||||
178.128.121.17
|
||||
178.175.167.17
|
||||
178.62.100.247
|
||||
180.165.29.129
|
||||
180.76.175.142
|
||||
181.104.43.225
|
||||
18.144.169.142
|
||||
18.144.86.189
|
||||
183.98.76.106
|
||||
185.100.212.141
|
||||
185.156.42.141
|
||||
185.156.42.211
|
||||
185.156.43.181
|
||||
185.178.47.173
|
||||
185.187.169.10
|
||||
185.38.148.2
|
||||
185.67.3.40
|
||||
189.219.16.249
|
||||
190.2.135.111
|
||||
192.252.215.125
|
||||
193.142.146.230
|
||||
193.169.241.19
|
||||
193.233.127.72
|
||||
193.32.162.82
|
||||
194.28.87.177
|
||||
202.165.15.88
|
||||
202.69.169.162
|
||||
203.171.18.62
|
||||
205.254.166.227
|
||||
206.212.244.18
|
||||
207.154.214.103
|
||||
209.97.136.129
|
||||
210.116.111.28
|
||||
210.16.103.246
|
||||
211.37.174.180
|
||||
212.227.146.182
|
||||
212.47.251.8
|
||||
213.209.159.56
|
||||
217.154.92.76
|
||||
217.160.162.192
|
||||
217.160.172.90
|
||||
221.120.34.164
|
||||
221.122.121.219
|
||||
222.108.39.109
|
||||
2.26.0.198
|
||||
2.27.42.94
|
||||
23.175.145.234
|
||||
27.155.92.28
|
||||
27.79.0.43
|
||||
27.79.1.152
|
||||
27.79.1.69
|
||||
27.79.2.106
|
||||
27.79.2.81
|
||||
27.79.2.88
|
||||
27.79.3.14
|
||||
27.79.40.209
|
||||
27.79.40.53
|
||||
27.79.40.99
|
||||
27.79.41.136
|
||||
27.79.41.138
|
||||
27.79.41.73
|
||||
27.79.4.30
|
||||
27.79.43.128
|
||||
27.79.43.239
|
||||
27.79.44.185
|
||||
27.79.45.122
|
||||
27.79.45.186
|
||||
27.79.45.243
|
||||
27.79.46.17
|
||||
27.79.46.194
|
||||
27.79.46.216
|
||||
27.79.47.20
|
||||
27.79.47.210
|
||||
27.79.47.52
|
||||
27.79.5.188
|
||||
27.79.5.212
|
||||
27.79.5.46
|
||||
27.79.7.163
|
||||
27.79.7.22
|
||||
34.118.255.39
|
||||
34.140.156.133
|
||||
34.140.239.78
|
||||
34.140.57.124
|
||||
34.140.6.40
|
||||
34.140.77.166
|
||||
34.140.84.143
|
||||
34.14.124.6
|
||||
34.14.127.78
|
||||
34.14.26.70
|
||||
34.14.94.132
|
||||
34.173.87.191
|
||||
34.22.170.134
|
||||
34.22.170.190
|
||||
34.22.181.240
|
||||
34.22.191.207
|
||||
34.22.206.213
|
||||
34.22.216.80
|
||||
34.22.219.80
|
||||
34.22.231.214
|
||||
34.22.249.41
|
||||
34.34.133.200
|
||||
34.34.160.10
|
||||
34.34.163.200
|
||||
34.34.172.120
|
||||
34.38.131.179
|
||||
34.38.135.188
|
||||
34.38.13.53
|
||||
34.38.142.34
|
||||
34.38.185.18
|
||||
34.38.220.109
|
||||
34.38.29.170
|
||||
34.38.33.18
|
||||
34.38.38.155
|
||||
34.38.5.115
|
||||
34.38.6.243
|
||||
34.38.64.123
|
||||
34.52.128.71
|
||||
34.52.170.246
|
||||
34.52.188.21
|
||||
34.52.204.179
|
||||
34.52.208.122
|
||||
34.52.221.98
|
||||
34.53.138.146
|
||||
34.53.140.122
|
||||
34.53.141.182
|
||||
34.53.155.90
|
||||
34.53.183.148
|
||||
34.53.189.10
|
||||
34.53.229.179
|
||||
34.53.250.34
|
||||
34.62.117.51
|
||||
34.62.125.18
|
||||
34.62.130.149
|
||||
34.62.141.86
|
||||
34.62.148.105
|
||||
34.62.180.175
|
||||
34.62.184.76
|
||||
34.62.196.33
|
||||
34.62.199.99
|
||||
34.62.215.103
|
||||
34.62.231.139
|
||||
34.62.232.201
|
||||
34.62.2.5
|
||||
34.62.34.27
|
||||
34.62.44.251
|
||||
34.76.119.193
|
||||
34.76.200.186
|
||||
34.76.9.196
|
||||
34.77.146.42
|
||||
34.77.183.8
|
||||
34.77.185.43
|
||||
34.77.201.55
|
||||
34.77.211.171
|
||||
34.77.245.99
|
||||
34.77.84.204
|
||||
34.78.111.164
|
||||
34.78.129.216
|
||||
34.78.132.179
|
||||
34.78.151.236
|
||||
34.78.154.150
|
||||
34.78.155.230
|
||||
34.78.157.177
|
||||
34.78.158.49
|
||||
34.78.182.9
|
||||
34.78.196.247
|
||||
34.78.21.97
|
||||
34.78.31.127
|
||||
34.78.69.136
|
||||
34.78.9.129
|
||||
34.79.163.128
|
||||
34.79.175.147
|
||||
34.79.191.233
|
||||
34.79.215.100
|
||||
34.79.224.24
|
||||
34.79.238.246
|
||||
34.79.6.105
|
||||
34.79.62.169
|
||||
34.79.72.179
|
||||
35.187.64.30
|
||||
35.190.196.156
|
||||
35.194.141.75
|
||||
35.195.125.240
|
||||
35.195.143.58
|
||||
35.195.148.6
|
||||
35.195.162.79
|
||||
35.195.165.181
|
||||
35.195.18.109
|
||||
35.195.222.221
|
||||
35.195.37.202
|
||||
35.195.40.98
|
||||
35.195.68.186
|
||||
35.195.69.175
|
||||
35.195.71.153
|
||||
35.195.85.179
|
||||
35.195.87.98
|
||||
35.195.90.213
|
||||
35.205.107.141
|
||||
35.205.145.95
|
||||
35.205.157.203
|
||||
35.205.178.61
|
||||
35.205.185.0
|
||||
35.205.205.195
|
||||
35.205.214.148
|
||||
35.205.232.103
|
||||
35.205.236.118
|
||||
35.205.244.229
|
||||
35.205.251.123
|
||||
35.205.36.247
|
||||
35.205.78.141
|
||||
35.205.96.69
|
||||
35.205.98.220
|
||||
35.233.113.241
|
||||
35.233.122.202
|
||||
35.233.15.213
|
||||
35.233.20.248
|
||||
35.233.28.146
|
||||
35.240.0.184
|
||||
35.240.56.214
|
||||
35.240.7.56
|
||||
35.240.80.222
|
||||
35.240.92.250
|
||||
35.241.141.196
|
||||
35.241.154.33
|
||||
35.241.164.4
|
||||
35.241.185.64
|
||||
35.241.250.0
|
||||
35.86.165.124
|
||||
35.87.1.37
|
||||
35.89.149.93
|
||||
35.91.227.129
|
||||
35.91.89.48
|
||||
35.92.116.30
|
||||
35.92.64.216
|
||||
36.253.9.69
|
||||
38.55.145.239
|
||||
43.135.124.152
|
||||
44.250.46.59
|
||||
45.139.122.80
|
||||
45.142.193.135
|
||||
45.148.10.183
|
||||
45.153.34.205
|
||||
45.156.22.81
|
||||
45.156.24.224
|
||||
45.39.12.34
|
||||
45.55.91.50
|
||||
45.82.13.133
|
||||
46.101.94.59
|
||||
46.62.207.157
|
||||
46.8.231.219
|
||||
49.173.65.19
|
||||
50.2.184.82
|
||||
50.6.228.52
|
||||
51.15.254.120
|
||||
51.15.51.204
|
||||
51.15.55.248
|
||||
51.158.155.6
|
||||
51.158.160.54
|
||||
51.159.175.158
|
||||
51.159.189.185
|
||||
5.129.238.185
|
||||
5.144.129.17
|
||||
52.53.177.79
|
||||
5.253.59.171
|
||||
5.255.122.180
|
||||
54.177.65.211
|
||||
54.193.42.43
|
||||
54.219.89.58
|
||||
54.67.94.209
|
||||
58.209.82.167
|
||||
58.226.230.112
|
||||
59.22.201.143
|
||||
62.210.237.9
|
||||
64.23.184.75
|
||||
68.183.8.104
|
||||
77.22.211.47
|
||||
80.94.92.168
|
||||
82.66.91.30
|
||||
83.145.42.126
|
||||
83.168.89.181
|
||||
85.11.167.8
|
||||
85.239.56.61
|
||||
86.48.25.218
|
||||
87.106.149.146
|
||||
87.121.84.136
|
||||
87.249.165.241
|
||||
88.149.145.190
|
||||
89.190.156.34
|
||||
91.210.169.154
|
||||
94.159.98.224
|
||||
94.183.177.120
|
||||
94.26.106.206
|
||||
95.182.98.181
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user