Mantis/CLAUDE.md
ParrotXray 5e146717d9
Fix/some issus (#14)
* wip

* feat: Add egress eBPF access control and fix Suricata HTTP port detection

* feat: adjust code with rustfmt

* feat: adjust code with rustfmt

* feat: Remove SERVICE stage from ingress eBPF pipeline
2026-05-23 16:35:27 +08:00

10 KiB

Mantis

Network intrusion detection system combining eBPF packet capture with ML-based anomaly detection and Suricata/Snort rule matching.

Language Convention

  • Conversation: Always use Traditional Chinese (繁體中文)
  • Files, docs, comments, code: Always use English
  • This applies to ALL generated content without exception

Dependency Policy

  • ML inference: ort-tract (pure Rust, no native ORT binary) Do not switch to native ORT without explicit discussion.
  • Async runtime: tokio only, do not add async-std or smol.
  • Logging: macros::log! only, do not add log crate or use tracing:: directly.
  • Locking: std::sync::Mutex is preferred; parking_lot::Mutex where performance matters.
  • ort version is pinned to =2.0.0-rc.12, do not upgrade without checking ort-tract compatibility.

Workspace Structure

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
mantis-frontend/ - Next.js web UI

Build

Full build requires eBPF toolchain and system libs (libelf, boost for vectorscan).

To type-check without eBPF (for ML/API changes):

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 .../mantis-ingress
  • couldn't read .../mantis-egress

mantis Source Layout

src/
├── core/
│   ├── app_state.rs   - AppState (Clone) shared across all axum handlers
│   ├── ebpf/          - XDP/AF_XDP packet capture, access control
│   └── infrastructure/- AppServices init, AppConfig, AppDb, GeoIP
├── detection/
│   ├── ml/            - ML inference pipeline
│   │   ├── engine.rs          - inference loop (tokio interval)
│   │   ├── flow_tracker.rs    - per-flow packet aggregation
│   │   ├── feature_extractor.rs - flow -> feature vector
│   │   ├── inference.rs       - sliding window + autoencoder MSE
│   │   ├── model_loader.rs    - ort Session creation
│   │   ├── config_loader.rs   - inference_config.json
│   │   ├── aggregator.rs      - attack event dedup/aggregation
│   │   └── traffic_logger.rs  - CSV recording mode
│   └── rule/          - Suricata/Snort rule matching (vectorscan)
├── model/
│   ├── error/         - one file per domain (ml, ebpf, http, auth, ...)
│   ├── log/           - one file per domain (ml, ebpf, http, auth, ...)
│   └── ml_detection.rs- shared ML types (FlowKey, DetectionResult, ...)
├── web/
│   ├── api/           - axum route handlers
│   ├── middleware/    - auth.rs: AuthenticatedUser (FromRequestParts)
│   └── websocket/     - alert, health, flow WebSocket handlers
└── utils/             - packet parsing, logging setup, boot time

Macros (always use these)

log!

Logs an error or loggable value. Routes to tracing level automatically.

use macros::log;
log!(MLError::ModelLoadFailed { path });
log!(MLLog::InferenceCompleted { total_flows, anomaly, benign, duration_ms, throughput });

traceable!

Defines an error enum in src/model/error/<domain>.rs.

traceable! {
    MLError {
        #[no_source]
        #[error("Failed to load ONNX model from: {path:?}")]
        ModelLoadFailed { path: PathBuf } => tracing::Level::ERROR,
    }
}

loggable!

Defines a log message enum in src/model/log/<domain>.rs.

loggable! {
    MLLog {
        #[error("ML artifacts loaded - {info}")]
        ModelsLoaded { info: String } => tracing::Level::INFO,
    }
}

Conventions

  • New error variant -> add to src/model/error/<domain>.rs using traceable!
  • New log message -> add to src/model/log/<domain>.rs using loggable!
  • Never use println!, eprintln!, or tracing:: directly — always use log!()
  • Shared data types (structs, enums used across modules) go in src/model/
  • Comments in English only, no special characters

ML Pipeline

Inference runs on a tokio interval (default 5s):

eBPF packet -> FlowTracker (per-flow stats)
                    -> FlowFeatures (extract + normalize ~80 features)
                         -> Inference (sliding window buffer per src_ip)
                              -> ort Session (LSTM autoencoder, MSE score)
                                   -> AttackAggregator -> MLAlert (WebSocket)

Inference engine: ort-tract (pure Rust, no native ORT binary required). ort::set_api(ort_tract::api()) must be called before any Session is created. Session is wrapped in Mutex<Session> because Session::run requires &mut self.

Config

Runtime config: config.toml ML artifacts: mantis/static/artifacts/

  • deep_autoencoder.onnx - LSTM autoencoder model
  • inference_config.json - window size, feature names, scaler params, threshold

Auth DB: static/db/app.db (SQLCipher-encrypted, path configurable via [Config.auth].db_path)

HTTP API

Base URL: http://<host>:8080

All routes that require auth expect Authorization: Bearer <token> header. Auth is disabled if [Config.auth] is absent from config.toml.

Auth

Method Path Auth Request body Response
POST /auth/login No {"username":"…","password":"…"} {"token":"<jwt>"}
GET /auth/me Bearer {"id":"…","username":"…","role":"…"}
POST /auth/logout Bearer 200 OK (stateless, client discards token)

JWT claims: { sub, username, role, exp }. Default TTL: 86400s. Default admin: username=admin, password=admin (seeded on first boot).

eBPF Access Control — /ebpf/access_control

Method Path Body Description
GET /{nic}/ipv4/{flow}/{list_type} Get IPv4 list
PUT /{nic}/ipv4/{flow}/{list_type} SocketAddrV4 JSON Add entry
DELETE /{nic}/ipv4/{flow}/{list_type} SocketAddrV4 JSON Remove entry
GET /{nic}/ipv6/{flow}/{list_type} Get IPv6 list
PUT /{nic}/ipv6/{flow}/{list_type} SocketAddrV6 JSON Add entry
DELETE /{nic}/ipv6/{flow}/{list_type} SocketAddrV6 JSON Remove entry

nic: ingress | egressflow: source | destinationlist_type: whitelist | blacklist

Ingress access control targets the ingress eBPF (inbound packets from external network). Egress access control targets the egress eBPF (outbound packets from internal network). source matches the packet's src IP/port; destination matches dst IP/port.

eBPF Service Control — /ebpf/service

Method Path Body Description
GET /ipv4/http_service List IPv4 HTTP service entries
PUT /ipv4/http_service [SocketAddrV4, [HttpMethod]] Add HTTP service
DELETE /ipv4/http_service [SocketAddrV4, [HttpMethod]] Remove HTTP service
GET /ipv6/http_service List IPv6 HTTP service entries
PUT /ipv6/http_service [SocketAddrV6, [HttpMethod]] Add HTTP service
DELETE /ipv6/http_service [SocketAddrV6, [HttpMethod]] Remove HTTP service
GET /ssh_white_list SSH whitelist enabled?
POST /ssh_white_list/enable Enable SSH whitelist
POST /ssh_white_list/disable Disable SSH whitelist
GET/PUT/DELETE /ipv4/ssh_service SocketAddrV4 SSH service list
GET/PUT/DELETE /ipv6/ssh_service SocketAddrV6 SSH service list
GET/PUT/DELETE /ipv4/ssh_white_list Ipv4Addr SSH whitelist
GET/PUT/DELETE /ipv6/ssh_white_list Ipv6Addr SSH whitelist
GET/PUT/DELETE /ipv4/ssh_black_list Ipv4Addr SSH blacklist
GET/PUT/DELETE /ipv6/ssh_black_list Ipv6Addr SSH blacklist

eBPF Statistics — /ebpf/statistics

Method Path Description
GET /get/ipv4/{direction}/{flow_direction}/{time_type} IPv4 flow data snapshot
GET /get/ipv6/{direction}/{flow_direction}/{time_type} IPv6 flow data snapshot
GET (WS) /websocket/ipv4/{direction}/{flow_direction}/{time_type} IPv4 flow stream
GET (WS) /websocket/ipv6/{direction}/{flow_direction}/{time_type} IPv6 flow stream

direction: inbound | outboundflow_direction: source | destination time_type: per_second | per_minute | per_hour

Health — /health

Method Path Description
GET /metrics Current system metrics (CPU, memory, etc.)
GET /status Boolean system health
GET (WS) /websocket/metrics Live metrics stream

Detection — /detection

Method Path Description
GET (WS) /websocket/alert Unified alert stream (UnifiedAlert JSON)

UnifiedAlert fields: source, severity, src_ip, dst_ip, proto, timestamp, detail

Misc — /misc

Method Path Description
GET /boot_time System boot timestamp

WebSocket Protocol

All WebSocket endpoints push JSON on data events. Clients should:

  • Respond to Ping frames with Pong (handled automatically by most WS clients)
  • Send Close frame to disconnect gracefully

HTTP Framework

axum 0.8 + tower-http. State is passed via AppState (Clone) registered with .with_state(). New handlers: add State(state): State<AppState> as parameter. Path params use {param} syntax. Auth-protected handlers: add user: AuthenticatedUser as parameter (auto-rejects invalid tokens).

// Example: auth-protected handler
async fn my_handler(
    user: AuthenticatedUser,
    State(state): State<AppState>,
) -> impl IntoResponse {
    // user.0.role, user.0.username available
}

Implementation Priority

See TODO for the full backlog.