Skip to content

Chạy foreground ​

Chạy trực tiếp từ terminal, hữu ích khi kiểm tra cấu hình lần đầu hoặc gỡ rối:

Linux:

bash
# Chạy với file blackhole-fwd.yml trong thư mục hiện tại
blackhole-fwd start

# Chỉ định file cấu hình khác
blackhole-fwd -c /path/to/blackhole-fwd.yml start

# Chạy với log level debug (tương đương đặt logging.level: "debug")
blackhole-fwd -v start

Windows (PowerShell):

powershell
.\blackhole-fwd.exe start
.\blackhole-fwd.exe -c "D:\siem\blackhole-fwd.yml" start
.\blackhole-fwd.exe -v start

Một số điểm cần nhớ:

  • Flag cấu hình là -c / --config. Mặc định là blackhole-fwd.yml trong thư mục làm việc; nếu file này chưa có, process thử ./config.yml rồi ./config.yaml.
  • Nếu file tại đúng đường dẫn --config không tồn tại, khởi động thoát ngay với lỗi Failed to read file for hashing: blackhole-fwd.yml, xem Troubleshooting.
  • Không truyền subcommand nào thì binary chỉ in banner, không chạy pipeline.
  • Trên Windows, process tự chuyển thư mục làm việc về thư mục chứa executable, nên config, log và database luôn nằm cạnh binary. Trên Linux, thư mục làm việc là nơi bạn khởi chạy từ đó.
📝
`-v` / `--verbose` ghi đè `logging.level` lên `debug` cho lần chạy đó. Không có flag `--log-level`. Muốn đổi level vĩnh viễn, sửa `logging.level` trong cấu hình rồi khởi động lại (logging không hot-reload).

Xác minh:

bash
tail -f logs/default.log
# kỳ vọng thấy: "Orchestrator started successfully"

Nếu cấu hình có section registry: với HTTP listener bật:

bash
curl -s http://127.0.0.1:18080/health
# kỳ vọng: {"status":"ok"}

Chạy như service ​

Cài và quản lý bằng CLI ​

Cần quyền administrator (Windows) hoặc root (Linux):

bash
blackhole-fwd service install        # cài service, cài xong tự khởi động
blackhole-fwd service status         # kiểm tra trạng thái
blackhole-fwd service start          # khởi động
blackhole-fwd service stop           # dừng
blackhole-fwd service uninstall      # gỡ service
⚠️
Không có lệnh `service restart` trong CLI. Muốn restart: `service stop` rồi `service start` (hoặc dùng `systemctl restart blackhole-fwd` / `Restart-Service blackhole-fwd` ở dưới).
⚠️
`service install` **đã khởi động service ngay**. Cài lại khi service đã tồn tại sẽ thất bại với `Service already exists. Use --force to overwrite.`, thêm `--force` để ghi đè.

Windows ​

powershell
Get-Service -Name "blackhole-fwd"        # trạng thái
Start-Service -Name "blackhole-fwd"
Stop-Service -Name "blackhole-fwd"
Restart-Service -Name "blackhole-fwd"    # stop + start

Linux ​

Unit file nằm ở /etc/systemd/system/blackhole-fwd.service (cần systemd hoặc OpenRC, cài bằng quyền root):

bash
sudo systemctl status blackhole-fwd
sudo systemctl start blackhole-fwd
sudo systemctl stop blackhole-fwd
sudo systemctl restart blackhole-fwd
sudo systemctl enable blackhole-fwd      # tự khởi động khi boot (đã enable sẵn khi cài)
sudo journalctl -u blackhole-fwd -f      # chỉ xem được phần stderr

Service dùng cấu hình nào ​

Service được ghi nhận là <đường dẫn executable> --service --service-name blackhole-fwd. Hai hệ quả:

⚠️
- `--config` truyền lúc `service install` **không được lưu lại**. Service luôn tự tìm cấu hình từ thư mục làm việc (= thư mục chứa executable): `blackhole-fwd.yml`, rồi `config.yml`, rồi `config.yaml`. - Service chạy bằng quyền **root** (Linux) hoặc **LocalSystem** (Windows), chưa có hạ quyền (privilege dropping).

Ở chế độ service, stdout bị tắt nên file log là nơi duy nhất có log runtime. journalctl chỉ nhận phần stderr, chủ yếu là thông báo Error: ... khiến process thoát ngay khi khởi động. Chi tiết xem Xem log.

Xác minh:

bash
blackhole-fwd service status
# kỳ vọng: ✅ Service is running

sudo systemctl is-enabled blackhole-fwd
# kỳ vọng: enabled
powershell
Get-Service -Name "blackhole-fwd"
# kỳ vọng: Status = Running

Chạy trong container ​

Image ghcr.io/gcsclabs/blackhole-forwarder được build trên nền distroless, lệnh mặc định là --config /etc/blackhole/blackhole-fwd.yml start:

bash
docker run -d --name blackhole-fwd \
  -v /host/config/blackhole-fwd.yml:/etc/blackhole/blackhole-fwd.yml:ro \
  -v blackhole-fwd-data:/home/nonroot \
  -p 514:514/udp -p 514:514/tcp \
  ghcr.io/gcsclabs/blackhole-forwarder:latest
  • Mount cấu hình read-only là đủ, file chỉ được đọc lúc khởi động và khi hot-reload phát hiện thay đổi.
  • Thư mục làm việc trong container là /home/nonroot: database blackhole-fwd.db và logs/ nằm ở đó. Mount volume tại /home/nonroot để giữ dữ liệu qua mỗi lần restart.
  • Publish các port theo bảng Port lắng nghe, chỉ publish port nào có section cấu hình tương ứng.
  • Image không có shell, nên không thể docker exec ... sh vào, xem Troubleshooting.

Chi tiết image và các biến thể cài, xem Cài đặt.

Xác minh:

bash
docker logs -f blackhole-fwd
# kỳ vọng thấy: "Orchestrator started successfully"

Dừng Forwarder ​

Chế độCách dừng
ForegroundCtrl+C (Linux/Windows) hoặc SIGTERM
Serviceservice stop, systemctl stop blackhole-fwd, Stop-Service blackhole-fwd
Containerdocker stop blackhole-fwd (gửi SIGTERM, process có xử lý)

Quy trình dừng là fast stop, chuỗi log khi dừng:

Shutdown signal received. Exiting.
Shutting down Orchestrator...
Stopping Orchestrator (fast stop)...
Stopped!
⚠️
Dừng bằng Ctrl+C / SIGTERM là fast stop: collector, transform và shipper được dừng nhưng shipper **không được flush**. Message còn trong hàng đợi sẽ được gửi lại từ local queue ở lần khởi động kế tiếp, không mất dữ liệu nhưng có thể gửi trùng. Nếu cần chắc chắn mọi message đã rời khỏi Forwarder, chờ sink xác nhận trước khi dừng.

Xác minh:

bash
tail -5 logs/default.log
# kỳ vọng dòng cuối: "Stopped!"

pgrep -x blackhole-fwd        # Linux: không in ra gì
powershell
Get-Process blackhole-fwd     # Windows: báo Cannot find a process (đã không còn process)

Xem log ​

Vị trí log ​

Log ghi relative tới thư mục làm việc của process:

FileNội dung
logs/default.logLog chính (info, debug, warn)
logs/default-error.logChỉ các dòng mức ERROR

Thư mục logs/ được tự động tạo khi khởi động.

Xác định thư mục làm việc ​

bash
# Linux, thư mục làm việc của process
readlink /proc/$(pgrep -x blackhole-fwd)/cwd
# hoặc: pwdx $(pgrep -x blackhole-fwd)

# Linux, process đang dùng config nào
sudo tr '\0' ' ' < /proc/$(pgrep -x blackhole-fwd)/cmdline; echo
  • Chạy foreground (Linux): thư mục bạn khởi chạy từ đó.
  • Chạy service (Linux): thư mục chứa executable (được set lúc service install).
  • Windows: process tự chuyển về thư mục chứa executable, nên log nằm ở <thư mục chứa blackhole-fwd.exe>\logs\. Xác định nhanh:
powershell
$exeDir = (Get-Process blackhole-fwd).Path | Split-Path
Get-ChildItem "$exeDir\logs"

Đọc log ​

Linux:

bash
tail -f logs/default.log                 # theo dõi log chính
tail -n 200 logs/default-error.log       # 200 dòng lỗi gần nhất
grep "ERROR" logs/default.log | tail -50

# Service: journal chỉ có stderr (lỗi khởi động làm process thoát)
sudo journalctl -u blackhole-fwd --since "10 minutes ago"

Windows:

powershell
Get-Content ".\logs\default.log" -Tail 50 -Wait
Get-Content ".\logs\default-error.log" -Tail 50
📝
Ở chế độ service, stdout bị tắt: console và journal **không có** log runtime, chỉ file log có. Release build dùng `panic = "abort"` và không giữ symbols, một panic làm process chết ngay mà không có backtrace Rust; khi đó, các dòng cuối cùng trong `logs/default.log` và `logs/default-error.log` là bằng chứng duy nhất, phần thông báo panic nằm trên stderr (journal).

Xác minh:

bash
ls -l logs/
# kỳ vọng thấy default.log và default-error.log, default.log tăng kích thước khi có sự kiện

grep "Orchestrator started successfully" logs/default.log | tail -1

Log rotation và kích thước ​

Rotation luôn bật, không có công tắc bật/tắt. Cách chia file:

FileSố file xoayCỡ mỗi fileTổng dung lượng
logs/default.log5max_size_mb / 5≈ max_size_mb
logs/default-error.log10max_size_mb≈ 10 × max_size_mb

Điều khiển bằng cấu hình logging (mặc định max_size_mb: 10):

yaml
logging:
  level: "info"
  dir: "./logs"
  file: "default.log"
  error_file: "default-error.log"
  max_size_mb: 10

Muốn đổi dung lượng hay vị trí: sửa logging rồi khởi động lại, section này không hot-reload. Tham chiếu đầy đủ tại Cấu hình logging.

📝
Không có cơ chế "bật log rotation", nó đã luôn bật. Cách điều chỉnh duy nhất là `logging.max_size_mb` (cùng `dir`, `file`, `error_file`). Không giới hạn theo số ngày hay thời gian.

Xác minh:

bash
du -sh logs/
ls logs/ | wc -l
# dung lượng ổn định quanh max_size_mb (log chính) dù process chạy lâu,
# số file không vượt 5 (log chính) và 10 (log lỗi)

Port lắng nghe ​

Mỗi port chỉ mở khi section cấu hình tương ứng tồn tại trong config:

PortGiao thứcMục đíchMở khiTrường bind mặc định
514UDP + TCPrsyslog sourcecó rsyslog_server (kèm source rsyslog)0.0.0.0:514, rsyslog_server.udp_addr, rsyslog_server.tcp_addr
162UDPNhận SNMP trapcó snmp_server (kèm source snmp)0.0.0.0:162, snmp_server.trap_addr
1883TCPEmbedded MQTT brokercó mqtt_server0.0.0.0:1883, mqtt_server.server.listen
50051TCPEmbedded gRPC servercó grpc_server và ít nhất một source grpc0.0.0.0:50051, grpc_server.listen_addr
8080TCPHTTP CONNECT proxycó proxy_server0.0.0.0:8080, proxy_server.addr
18080TCPHTTP next-hop (REST forwarder) + /healthcó registry: và registry.http_server.enabled (mặc định true)0.0.0.0:18080, registry.http_server.listen_addr

Ngoài ra Forwarder có kết nối ra ngoài tới SNMP agent ở cổng 161 khi cấu hình poll.

⚠️
Cổng 8080 rất hay bị service khác chiếm (web server, JVM…). Nếu bật `proxy_server` mà 8080 đã có process khác nghe, đổi `proxy_server.addr` hoặc dừng service kia. Tương tự, các cổng dưới 1024 như 514 và 162 chỉ bind được khi chạy với quyền root (chế độ service thì đã chạy root).

Cú pháp từng section server: xem Embedded servers.

Xác minh:

bash
# Linux, chỉ ra các port đang nghe và process sở hữu
sudo ss -tulnp | grep -E ':(514|162|1883|50051|8080|18080)'
# hoặc
sudo netstat -tulnp | grep -E ':(514|162|1883|50051|8080|18080)'
powershell
# Windows
netstat -an | findstr ":18080"
netstat -an | findstr ":50051"

Kỳ vọng: mỗi port có dòng LISTEN/udp gắn với process blackhole-fwd, chỉ với port có section cấu hình bật. Port thiếu section thì không xuất hiện.

Health endpoint và route next-hop ​

Khi cấu hình có section registry: và registry.http_server.enabled (mặc định true), Forwarder mở HTTP listener (mặc định 0.0.0.0:18080) làm next-hop: chuyển tiếp request của thiết bị con lên Registry và expose endpoint kiểm tra trạng thái cục bộ.

📝
Đây là endpoint **của chính Forwarder**, không phải của Registry, nên test trên `127.0.0.1:18080`. Khi bật listener này, startup yêu cầu `registry.api_key` và device id hợp lệ, nếu thiếu sẽ thoát ngay, xem [Troubleshooting](./troubleshooting#thieu-api-key).

Các route ​

MethodRouteHeader bắt buộcGhi chú
GET/healthKhông cóTrả về {"status":"ok"}
GET/client/configx-device-ip-addressChuyển tiếp lên Registry. Khi Registry trả 401/404, tự auto-join nếu có thêm x-device-name và x-device-type
PATCH/client/configx-device-ip-addressBody được chuyển tiếp nguyên vẹn
POST/client/alertsx-device-ip-addressBody JSON, tối đa 1 MiB
POST/devices/joinKhông có (thông tin thiết bị nằm trong body)Đăng ký thiết bị con

Ví dụ curl ​

bash
# 1. Health
curl -s http://127.0.0.1:18080/health
# {"status":"ok"}

# 2. Lấy cấu hình cho một thiết bị
curl -s -H "x-device-ip-address: 10.0.0.5" http://127.0.0.1:18080/client/config

# 3. Thiếu header bắt buộc → 400
curl -s http://127.0.0.1:18080/client/config
# {"error":"missing required header x-device-ip-address"}

# 4. Gửi alert (body JSON, tối đa 1 MiB)
curl -s -X POST \
  -H "x-device-ip-address: 10.0.0.5" \
  -H "Content-Type: application/json" \
  -d '{"event":"connectivity-test"}' \
  http://127.0.0.1:18080/client/alerts

Xác minh:

bash
curl -s http://127.0.0.1:18080/health
# kỳ vọng đúng: {"status":"ok"}

curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:18080/client/config
# kỳ vọng: 400 (thiếu x-device-ip-address)

Route config, alerts và join là next-hop: status trả về chính là status của Registry trả về. Nếu không tới được Registry, route trả 502 kèm body {"error": ...}. Chi tiết cấu hình listener: xem Cổng REST next-hop.

Hot-reload ​

Cơ chế ​

  • Phát hiện thay đổi bằng cách poll SHA-256 mỗi hot_reload.poll_interval_ms (mặc định 5000 ms), với debounce hot_reload.debounce_ms (mặc định 1000 ms). hot_reload.enabled mặc định true.
  • Không dùng inotify, không cần SIGHUP, chỉ cần file cấu hình thay đổi nội dung là được.

Thay đổi local ​

Một thay đổi local được phát hiện sẽ:

  1. Đọc lại cấu hình vào bộ nhớ,
  2. Ghi lại snapshot .blackhole-resolved.yaml cạnh file cấu hình,
  3. Log Local config reloaded successfully.
⚠️
Thay đổi local **không dựng lại** collector, transform hay sink, nó chỉ nạp lại cấu hình vào bộ nhớ và ghi snapshot. Muốn component mới có hiệu lực thật sự, hãy khởi động lại service (hoặc để Registry push update, xem dưới).

Nếu nguồn cấu hình đang active là Registry, mọi sửa local bị bỏ qua hẳn: log Remote config is active — ignoring local config change (N files).

Update do Registry push ​

Chỉ update từ Registry mới chạy diff theo identifier để thêm, bỏ hoặc khởi động lại từng component riêng lẻ. Cấu hình remote không hợp lệ bị từ chối và giữ bản cũ: log Remote config validation failed: ..., skipping update.

Nguồn cấu hình lúc khởi động có thể là local, persisted (trong database) hoặc remote, xem dòng log Resolved config snapshot written (source: ...) và file .blackhole-resolved.yaml.

Không hot-reload được (phải khởi động lại) ​

logging, hot_reload, registry, resources_threshold, channel_buffers và mọi embedded server section: rsyslog_server, snmp_server, mqtt_server, proxy_server, grpc_server, cùng registry.http_server.

Chi tiết phạm vi hot-reload: xem Hot-reload.

Xác minh:

bash
printf '\n# reload test %s\n' "$(date +%s)" >> blackhole-fwd.yml
sleep 7
grep "Local config reloaded successfully" logs/default.log | tail -1
ls -l .blackhole-resolved.yaml

Kỳ vọng: dòng Local config reloaded successfully xuất hiện sau tối đa ~6 giây (poll 5 s + debounce 1 s) và snapshot được ghi lại. Nếu thay vào đó log báo Remote config is active — ignoring local config change, thì cấu hình đang do Registry quản lý.

Resource guard ​

Cấu hình ​

yaml
resources_threshold:
  cpu:
    threshold_percentage: 80.0
    sustained_secs: 60
  memory:
    threshold_percentage: 80.0
    sustained_secs: 60
  disk:
    threshold_percentage: 101.0   # > 100 nghĩa là tắt theo dõi disk
    sustained_secs: 60
  check_interval: 10              # giây

Theo dõi một metric chỉ hoạt động khi threshold_percentage <= 100. Mặc định: CPU 80%, memory 80%, disk 101% (tắt), kiểm tra mỗi 10 giây, ngưng 60 giây liên tục mới tính là vượt ngưỡng.

Hành vi ​

Thời điểmHành vi
Lúc khởi độngMetric đã bật mà ≥ ngưỡng → process thoát ngay với Startup blocked due to resource constraints: ...
Đang chạyVượt ngưỡng liên tục qua sustained_secs → tạm dừng pipeline với log PAUSING ORCHESTRATOR due to resource constraints: collector, transform, shipper dừng, queue được flush, không drop event, không tắt process
Khi hồi phụcPipeline chạy lại với log RESUMING ORCHESTRATOR - resources have stabilized
📝
Ngưỡng được đọc **một lần lúc khởi động**. Sửa `resources_threshold` trong khi đang chạy không có tác dụng, phải khởi động lại service.

Xác minh:

bash
grep -E "PAUSING ORCHESTRATOR|RESUMING ORCHESTRATOR" logs/default.log | tail -10
# kỳ vọng có cả hai loại dòng khi sự kiện vượt ngưỡng rồi hồi phục

grep "Startup blocked due to resource constraints" logs/default.log
# kỳ vọng: không có (nếu khởi động thành công)

Device guard ​

Device guard là danh sách chặn (deny-list) token thiết bị, giữ trong bộ nhớ, được cập nhật từ Registry theo trạng thái thiết bị, không phải allow-list đọc từ file cấu hình:

  • Không có khóa YAML nào cấu hình device guard.
  • Danh sách rỗng nghĩa là cho phép mọi token, không phải chặn mọi token.
  • Một device không ở trạng thái Active thì token của nó bị đưa vào danh sách; request gRPC mang metadata x-device-token bị từ chối với permission_denied (device is disabled by supervisor guard).
  • Stream gRPC đang mở với token đó bị ngắt ngay khi token bị chặn, log Terminating gRPC stream because device token <token> was disabled by supervisor guard.
  • Ở nhánh HTTP next-hop, token đi kèm header x-device-token khi chuyển tiếp lên Registry.

Cách xử lý khi một thiết bị bị từ chối: đặt lại trạng thái Active cho device đó ở Registry (token được gỡ khỏi deny-list). Request mới sẽ thông qua, nhưng stream đã bị ngắt phải kết nối lại.

Xác minh:

bash
grep "disabled by supervisor guard" logs/default.log | tail -5
# kỳ vọng: các dòng này xuất hiện khi device không Active,
# không còn dòng mới sau khi device chuyển sang Active

Chi tiết cấu hình phía Registry: xem Device guard.

Đồng bộ cấu hình và xác thực ​

Các lệnh dùng hằng ngày khi có section registry::

bash
# Xác thực với Registry: đăng ký device rồi push cấu hình local lên
blackhole-fwd auth --key "<API key>"

# Kéo cấu hình từ Registry về (ghi vào file local; --output để chỉ đích file)
blackhole-fwd configure pull --output ./pulled-config.yml

# Đẩy cấu hình local lên Registry
blackhole-fwd configure push

# Sinh JSON schema của cấu hình ra file config.jsonschema cạnh file config
blackhole-fwd configure schema
📝
- `auth` yêu cầu cấu hình đã có `registry.api_url`, thiếu sẽ báo `No registry configured`. - `configure push` trước khi auth sẽ thất bại với `device token not available, you need to authenticate first`. - Không có lệnh `config validate`, validation xảy ra lúc khởi động (lỗi parse) và lúc nhận config từ Registry. `configure schema` sinh JSON schema để kiểm tra thủ công.

Xác minh:

bash
blackhole-fwd auth --key "<API key>"
# kỳ vọng log/console: "Authentication successful!" và
# "Configuration pushed to remote server successfully"

# (khi đã cấu hình registry: và đã auth)
blackhole-fwd configure pull --output ./pulled-config.yml
ls -l pulled-config.yml
# kỳ vọng: file tồn tại và có nội dung YAML

blackhole-fwd configure push; echo $?
# kỳ vọng: 0

blackhole-fwd configure schema
ls -l config.jsonschema
# kỳ vọng: file JSON schema nằm cạnh file cấu hình

Chi tiết: xem Registry.

Backup cấu hình và trạng thái ​

Thành phần cần backup ​

Thành phầnVị trí
File cấu hìnhblackhole-fwd.yml (đường dẫn đang dùng)
Snapshot cấu hình hiệu lực.blackhole-resolved.yaml, nằm cạnh file cấu hình
Database + persistent queue./blackhole-fwd.db, là một thư mục (sled), chứa cả hàng đợi tin nhắn chưa gửi
Log (tuỳ chọn)./logs/

Tìm đường dẫn đang dùng ​

bash
# Linux
sudo tr '\0' ' ' < /proc/$(pgrep -x blackhole-fwd)/cmdline; echo
sudo readlink /proc/$(pgrep -x blackhole-fwd)/cwd
# Service (Linux): không có --config trong cmdline,
# config nằm trong thư mục chứa executable
powershell
# Windows
Get-CimInstance Win32_Process -Filter "Name='blackhole-fwd.exe'" |
  Select-Object -ExpandProperty CommandLine
$exeDir = (Get-Process blackhole-fwd).Path | Split-Path

Backup ​

Dừng service trước khi sao chép, database đang được process giữ:

bash
sudo systemctl stop blackhole-fwd

sudo mkdir -p /backup/blackhole-fwd
sudo cp -r /path/to/blackhole-fwd.yml /backup/blackhole-fwd/
sudo cp -r /path/to/.blackhole-resolved.yaml /backup/blackhole-fwd/
sudo cp -r /path/to/blackhole-fwd.db /backup/blackhole-fwd/

sudo systemctl start blackhole-fwd
powershell
$exeDir = (Get-Process blackhole-fwd).Path | Split-Path
Stop-Service blackhole-fwd
Copy-Item "$exeDir\blackhole-fwd.yml" D:\backup\
Copy-Item "$exeDir\.blackhole-resolved.yaml" D:\backup\
Copy-Item "$exeDir\blackhole-fwd.db" D:\backup\blackhole-fwd.db -Recurse
Start-Service blackhole-fwd

Phục hồi: đặt các file trở lại đúng chỗ rồi khởi động lại service.

📝
Forwarder **không có** tính năng versioning/changelog cấu hình, không có "test config before apply" và không tự cập nhật binary từ xa. Validation chỉ xảy ra lúc khởi động và khi nhận config từ Registry. Do đó backup tay như trên là cách duy nhất giữ lịch sử.

Xác minh:

bash
ls -l /backup/blackhole-fwd
# kỳ vọng: blackhole-fwd.yml, .blackhole-resolved.yaml, blackhole-fwd.db/ (thư mục)

sudo systemctl start blackhole-fwd
grep "Resolved config snapshot written" logs/default.log | tail -1
# kỳ vọng: dòng mới với source phù hợp (local/persisted/remote)

Kiểm tra phiên bản ​

bash
blackhole-fwd --version
# hoặc
blackhole-fwd -V
powershell
.\blackhole-fwd.exe --version

Kỳ vọng in ra dòng dạng blackhole-fwd <phiên bản> (ví dụ blackhole-fwd 0.7.5). Dùng blackhole-fwd --help để xem toàn bộ flag và subcommand hợp lệ.

Xem thêm ​

Released under the MIT License.