mirror of
https://github.com/DaLaw2/NetGuardia.git
synced 2026-08-24 14:10:28 +09:00
test: harden dev topology and integration gate
This commit is contained in:
parent
eee63a558a
commit
76572d323d
@ -1,6 +1,6 @@
|
||||
# NetGuardia
|
||||
|
||||
Inline network security platform built on eBPF/XDP. Runs four independent detectors (per-packet ML, temporal beaconing, graph correlation, Suricata) over the same data plane, fuses their verdicts, drives SOAR playbooks, and writes every decision into a WORM audit chain.
|
||||
Inline network security platform built on eBPF/XDP. Combines ONNX-based ML, temporal beaconing, correlation heuristics, and Suricata `eve.json` alerts in one fusion path, drives SOAR playbooks, and writes decisions into a WORM audit chain.
|
||||
|
||||
## Stack
|
||||
|
||||
@ -8,7 +8,7 @@ Inline network security platform built on eBPF/XDP. Runs four independent detect
|
||||
- **Detection** — Rust + tract-onnx for ML, custom temporal / graph engines, Suricata `eve.json` ingest
|
||||
- **Control plane** — actix-web REST + WebSocket, SQLite + SQLCipher, argon2 / JWT / CSRF, per-playbook SOAR
|
||||
- **Frontend** — Vue 3 + Pinia + Vue-i18n (en / zh-TW / zh-CN / ja)
|
||||
- **Architecture** — hexagonal: `adapter/` · `core/` · `infrastructure/` · `interface/` · `model/`
|
||||
- **Architecture** — hexagonal-ish Rust workspace: `domain/` · `interface/` · `core/` · `adapter/` · `infrastructure/`
|
||||
|
||||
## Screens
|
||||
|
||||
|
||||
@ -17,7 +17,6 @@ RUN dnf install -y epel-release && \
|
||||
git \
|
||||
gh \
|
||||
vim \
|
||||
openssh-server \
|
||||
ethtool \
|
||||
nodejs24 \
|
||||
nodejs24-npm \
|
||||
@ -39,9 +38,5 @@ RUN ln -s /usr/bin/node-24 /usr/local/bin/node && \
|
||||
ln -s /usr/bin/npm-24 /usr/local/bin/npm && \
|
||||
ln -s /usr/bin/npx-24 /usr/local/bin/npx
|
||||
|
||||
RUN echo 'root:@Server20040421@' | chpasswd && \
|
||||
sed -i 's/^#PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config && \
|
||||
sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
|
||||
|
||||
WORKDIR /root/NetGuardia
|
||||
CMD sh -c "ssh-keygen -A && /usr/sbin/sshd && sleep infinity"
|
||||
CMD ["sleep", "infinity"]
|
||||
|
||||
@ -22,7 +22,6 @@ services:
|
||||
mgmt-net:
|
||||
ipv4_address: 10.10.3.10
|
||||
ports:
|
||||
- "2222:22"
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- /home/dalaw2/NetGuardia:/root/NetGuardia:z
|
||||
|
||||
@ -1,146 +1,465 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
#!/usr/bin/env bash
|
||||
# Build the NetGuardia development containers and inline veth topology.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DEPLOY_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
COMPOSE_FILE="$DEPLOY_DIR/compose/podman-compose.yml"
|
||||
ROOT_DIR="$(cd "$DEPLOY_DIR/.." && pwd)"
|
||||
BASE_COMPOSE_FILE="$DEPLOY_DIR/compose/podman-compose.yml"
|
||||
COMPOSE_FILE="/tmp/netguardia-compose-$$.yml"
|
||||
COMPOSE_PROJECT="compose"
|
||||
LOG_FILE="/tmp/netguardia-dev-$(date +%Y%m%d-%H%M%S).log"
|
||||
|
||||
if command -v podman-compose &>/dev/null; then
|
||||
COMPOSE="podman-compose -f $COMPOSE_FILE"
|
||||
RT="podman"
|
||||
elif command -v docker &>/dev/null && docker compose version &>/dev/null 2>&1; then
|
||||
COMPOSE="docker compose -f $COMPOSE_FILE"
|
||||
RT="docker"
|
||||
else
|
||||
echo "ERROR: No container runtime found"
|
||||
exit 1
|
||||
fi
|
||||
VERBOSE=0
|
||||
CLEANUP_FIRST=1
|
||||
RT=""
|
||||
declare -a RT_CMD=()
|
||||
declare -a COMPOSE_CMD=()
|
||||
|
||||
echo "=== Runtime: $RT ==="
|
||||
echo "=== Kernel: $(uname -r) ==="
|
||||
echo ""
|
||||
|
||||
echo "=== Building containers ==="
|
||||
$COMPOSE build
|
||||
|
||||
echo "=== Starting containers ==="
|
||||
$COMPOSE up -d
|
||||
|
||||
echo ""
|
||||
echo "=== Containers running ==="
|
||||
$RT ps --format "table {{.Names}}\t{{.Status}}" 2>/dev/null || $RT ps
|
||||
|
||||
get_pid() {
|
||||
$RT inspect --format '{{.State.Pid}}' "$1"
|
||||
info() {
|
||||
printf '[INFO] %s\n' "$*"
|
||||
}
|
||||
|
||||
mkdir -p /var/run/netns
|
||||
warn() {
|
||||
printf '[WARN] %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
EXT_PID=$(get_pid external)
|
||||
INT_PID=$(get_pid internal)
|
||||
RTR_PID=$(get_pid router)
|
||||
NG_PID=$(get_pid netguardia)
|
||||
ln -sf /proc/$EXT_PID/ns/net /var/run/netns/external
|
||||
ln -sf /proc/$INT_PID/ns/net /var/run/netns/internal
|
||||
ln -sf /proc/$RTR_PID/ns/net /var/run/netns/router
|
||||
ln -sf /proc/$NG_PID/ns/net /var/run/netns/netguardia
|
||||
fatal() {
|
||||
printf '[ERROR] %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Segment 1: external <-> router (10.10.1.0/24)
|
||||
# Direct connection, no inspection needed
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "=== Segment 1: external <-> router (10.10.1.0/24) ==="
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: sudo bash deploy/scripts/dev.sh [--verbose] [--no-cleanup]
|
||||
|
||||
ip link add ext-eth0 type veth peer name rtr-ext
|
||||
ip link set ext-eth0 netns external
|
||||
ip link set rtr-ext netns router
|
||||
Options:
|
||||
--verbose Print compose build/up output in addition to writing the log.
|
||||
--no-cleanup Skip the default preflight cleanup of old containers/veth links.
|
||||
-h, --help Show this help.
|
||||
EOF
|
||||
}
|
||||
|
||||
ip netns exec external ip link set lo up
|
||||
ip netns exec external ip link set ext-eth0 up
|
||||
ip netns exec external ip addr add 10.10.1.2/24 dev ext-eth0
|
||||
for i in 3 4 5 6 7; do
|
||||
ip netns exec external ip addr add 10.10.1.${i}/24 dev ext-eth0
|
||||
done
|
||||
ip netns exec external ip route add default via 10.10.1.1
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--verbose)
|
||||
VERBOSE=1
|
||||
shift
|
||||
;;
|
||||
--no-cleanup)
|
||||
CLEANUP_FIRST=0
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
fatal "Unknown argument: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
ip netns exec router ip link set lo up
|
||||
ip netns exec router ip link set rtr-ext up
|
||||
ip netns exec router ip addr add 10.10.1.1/24 dev rtr-ext
|
||||
cleanup_netns_links() {
|
||||
rm -f \
|
||||
/var/run/netns/external \
|
||||
/var/run/netns/internal \
|
||||
/var/run/netns/router \
|
||||
/var/run/netns/netguardia
|
||||
}
|
||||
|
||||
echo " external: ext-eth0 10.10.1.{2-7}/24, gw 10.10.1.1"
|
||||
echo " router: rtr-ext 10.10.1.1/24"
|
||||
cleanup_temp_files() {
|
||||
cleanup_netns_links
|
||||
rm -f "$COMPOSE_FILE"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Segment 2: router <-> netguardia <-> internal (10.10.2.0/24)
|
||||
# NetGuardia inline: XDP on ng-ext (router side) and ng-int (internal side)
|
||||
# No bridges, no inline veth pair — direct XSK forwarding
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "=== Segment 2: router <-> [NetGuardia] <-> internal (10.10.2.0/24) ==="
|
||||
trap cleanup_temp_files EXIT
|
||||
|
||||
# router <-> netguardia: ng-ext is the netguardia side
|
||||
ip link add rtr-int type veth peer name ng-ext
|
||||
ip link set rtr-int netns router
|
||||
ip link set ng-ext netns netguardia
|
||||
require_root() {
|
||||
[[ "$(id -u)" -eq 0 ]] || fatal "dev.sh must run as root. Use: sudo bash deploy/scripts/dev.sh"
|
||||
}
|
||||
|
||||
# netguardia <-> internal: ng-int is the netguardia side
|
||||
ip link add int-eth0 type veth peer name ng-int
|
||||
ip link set int-eth0 netns internal
|
||||
ip link set ng-int netns netguardia
|
||||
require_linux_host() {
|
||||
[[ "$(uname -s)" == "Linux" ]] || fatal "dev.sh supports Linux hosts only."
|
||||
if grep -qiE 'microsoft|wsl' /proc/version 2>/dev/null; then
|
||||
fatal "WSL2 is not supported for this XDP/AF_XDP development topology."
|
||||
fi
|
||||
}
|
||||
|
||||
# Router internal side
|
||||
ip netns exec router ip link set rtr-int up
|
||||
ip netns exec router ip addr add 10.10.2.1/24 dev rtr-int
|
||||
ip netns exec router sh -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'
|
||||
package_manager() {
|
||||
if command -v dnf >/dev/null 2>&1; then
|
||||
printf 'dnf'
|
||||
elif command -v apt-get >/dev/null 2>&1; then
|
||||
printf 'apt-get'
|
||||
elif command -v zypper >/dev/null 2>&1; then
|
||||
printf 'zypper'
|
||||
elif command -v pacman >/dev/null 2>&1; then
|
||||
printf 'pacman'
|
||||
fi
|
||||
}
|
||||
|
||||
# Internal container
|
||||
ip netns exec internal ip link set lo up
|
||||
ip netns exec internal ip link set int-eth0 up
|
||||
ip netns exec internal ip addr add 10.10.2.2/24 dev int-eth0
|
||||
for i in 3 4 5 6; do
|
||||
ip netns exec internal ip addr add 10.10.2.${i}/24 dev int-eth0
|
||||
done
|
||||
ip netns exec internal ip route add default via 10.10.2.1
|
||||
package_for_command() {
|
||||
local manager="$1"
|
||||
local command_name="$2"
|
||||
|
||||
# NetGuardia interfaces (no IP, transparent)
|
||||
ip netns exec netguardia ip link set ng-ext up
|
||||
ip netns exec netguardia ip link set ng-int up
|
||||
case "$manager:$command_name" in
|
||||
dnf:ip) printf 'iproute' ;;
|
||||
dnf:ping) printf 'iputils' ;;
|
||||
dnf:ethtool) printf 'ethtool' ;;
|
||||
dnf:curl) printf 'curl' ;;
|
||||
dnf:ln|dnf:mkdir|dnf:rm|dnf:uname) printf 'coreutils' ;;
|
||||
apt-get:ip) printf 'iproute2' ;;
|
||||
apt-get:ping) printf 'iputils-ping' ;;
|
||||
apt-get:ethtool) printf 'ethtool' ;;
|
||||
apt-get:curl) printf 'curl' ;;
|
||||
apt-get:ln|apt-get:mkdir|apt-get:rm|apt-get:uname) printf 'coreutils' ;;
|
||||
zypper:ip) printf 'iproute2' ;;
|
||||
zypper:ping) printf 'iputils' ;;
|
||||
zypper:ethtool) printf 'ethtool' ;;
|
||||
zypper:curl) printf 'curl' ;;
|
||||
zypper:ln|zypper:mkdir|zypper:rm|zypper:uname) printf 'coreutils' ;;
|
||||
pacman:ip) printf 'iproute2' ;;
|
||||
pacman:ping) printf 'iputils' ;;
|
||||
pacman:ethtool) printf 'ethtool' ;;
|
||||
pacman:curl) printf 'curl' ;;
|
||||
pacman:ln|pacman:mkdir|pacman:rm|pacman:uname) printf 'coreutils' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Disable checksum offload on ALL veth endpoints.
|
||||
# AF_XDP TX bypasses the kernel stack, so checksums are not computed.
|
||||
# Without this, TCP packets forwarded through XSK have bad checksums and get dropped.
|
||||
ip netns exec router ethtool -K rtr-int tx off rx off 2>/dev/null || true
|
||||
ip netns exec router ethtool -K rtr-ext tx off rx off 2>/dev/null || true
|
||||
ip netns exec internal ethtool -K int-eth0 tx off rx off 2>/dev/null || true
|
||||
ip netns exec external ethtool -K ext-eth0 tx off rx off 2>/dev/null || true
|
||||
ip netns exec netguardia ethtool -K ng-ext tx off rx off 2>/dev/null || true
|
||||
ip netns exec netguardia ethtool -K ng-int tx off rx off 2>/dev/null || true
|
||||
append_unique() {
|
||||
local value="$1"
|
||||
shift
|
||||
local existing
|
||||
|
||||
echo " router: rtr-int (10.10.2.1) <-> ng-ext (XDP ingress)"
|
||||
echo " netguardia: ng-ext <-> [XSK forwarding] <-> ng-int"
|
||||
echo " internal: int-eth0 (10.10.2.{2-6}) <-> ng-int (XDP egress)"
|
||||
echo " checksum offload disabled on all veth endpoints"
|
||||
for existing in "$@"; do
|
||||
[[ "$existing" == "$value" ]] && return 1
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Verify
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "=== Interfaces inside netguardia ==="
|
||||
ip netns exec netguardia ip -br link show
|
||||
install_packages() {
|
||||
local manager="$1"
|
||||
shift
|
||||
|
||||
echo ""
|
||||
echo "=== Testing connectivity ==="
|
||||
case "$manager" in
|
||||
dnf)
|
||||
dnf install -y "$@"
|
||||
;;
|
||||
apt-get)
|
||||
DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y "$@"
|
||||
;;
|
||||
zypper)
|
||||
zypper --non-interactive install "$@"
|
||||
;;
|
||||
pacman)
|
||||
pacman -Sy --noconfirm "$@"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
echo -n " external -> router: "
|
||||
ip netns exec external ping -c 1 -W 2 10.10.1.1 >/dev/null 2>&1 && echo "OK" || echo "FAIL"
|
||||
manual_install_command() {
|
||||
local manager="$1"
|
||||
shift
|
||||
|
||||
# Without net-guardia, traffic between router and internal won't pass
|
||||
# because ng-ext/ng-int are just veth endpoints with no forwarding
|
||||
echo -n " router -> internal: "
|
||||
ip netns exec router ping -c 1 -W 2 10.10.2.2 >/dev/null 2>&1 && echo "OK" || echo "FAIL (expected - needs net-guardia)"
|
||||
case "$manager" in
|
||||
dnf) printf 'dnf install -y %s\n' "$*" ;;
|
||||
apt-get) printf 'apt-get update && apt-get install -y %s\n' "$*" ;;
|
||||
zypper) printf 'zypper --non-interactive install %s\n' "$*" ;;
|
||||
pacman) printf 'pacman -Sy --noconfirm %s\n' "$*" ;;
|
||||
*) printf 'Install packages manually: %s\n' "$*" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cat > /tmp/netguardia_interfaces.txt << IEOF
|
||||
check_host_tools() {
|
||||
local required_commands=(ip ping ethtool curl ln mkdir rm uname)
|
||||
local missing_commands=()
|
||||
local packages=()
|
||||
local command_name manager package answer
|
||||
|
||||
for command_name in "${required_commands[@]}"; do
|
||||
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||
missing_commands+=("$command_name")
|
||||
fi
|
||||
done
|
||||
|
||||
if ((${#missing_commands[@]} == 0)); then
|
||||
info "Host tools OK"
|
||||
return 0
|
||||
fi
|
||||
|
||||
manager="$(package_manager || true)"
|
||||
[[ -n "$manager" ]] || fatal "Missing host commands: ${missing_commands[*]}. No supported package manager found."
|
||||
|
||||
for command_name in "${missing_commands[@]}"; do
|
||||
package="$(package_for_command "$manager" "$command_name")"
|
||||
[[ -n "$package" ]] || fatal "No package mapping for missing command '$command_name' on $manager."
|
||||
if append_unique "$package" "${packages[@]}"; then
|
||||
packages+=("$package")
|
||||
fi
|
||||
done
|
||||
|
||||
warn "Missing host commands: ${missing_commands[*]}"
|
||||
warn "Package manager: $manager"
|
||||
warn "Packages to install: ${packages[*]}"
|
||||
|
||||
if [[ ! -t 0 ]]; then
|
||||
manual_install_command "$manager" "${packages[@]}" >&2
|
||||
fatal "Non-interactive shell; refusing to install packages without consent."
|
||||
fi
|
||||
|
||||
read -r -p "Install missing packages? [y/N] " answer
|
||||
case "$answer" in
|
||||
y|Y|yes|YES)
|
||||
install_packages "$manager" "${packages[@]}"
|
||||
;;
|
||||
*)
|
||||
manual_install_command "$manager" "${packages[@]}" >&2
|
||||
fatal "Required host packages were not installed."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
runtime_install_hint() {
|
||||
cat >&2 <<'EOF'
|
||||
Install a supported container runtime first.
|
||||
|
||||
Examples:
|
||||
dnf install -y podman podman-compose
|
||||
apt-get install -y podman podman-compose
|
||||
apt-get install -y docker.io docker-compose-plugin
|
||||
EOF
|
||||
}
|
||||
|
||||
check_docker_supported() {
|
||||
local context security_options operating_system
|
||||
|
||||
context="$(docker context show 2>/dev/null || true)"
|
||||
if [[ "$context" == "desktop-linux" ]]; then
|
||||
fatal "Docker Desktop is not supported for this XDP/netns topology."
|
||||
fi
|
||||
|
||||
operating_system="$(docker info --format '{{.OperatingSystem}}' 2>/dev/null || true)"
|
||||
if [[ "$operating_system" == *"Docker Desktop"* ]]; then
|
||||
fatal "Docker Desktop is not supported for this XDP/netns topology."
|
||||
fi
|
||||
|
||||
security_options="$(docker info --format '{{json .SecurityOptions}}' 2>/dev/null || true)"
|
||||
if grep -qi rootless <<<"$security_options"; then
|
||||
fatal "Rootless Docker is not supported for this privileged XDP/netns topology."
|
||||
fi
|
||||
}
|
||||
|
||||
detect_runtime() {
|
||||
if command -v podman-compose >/dev/null 2>&1 && command -v podman >/dev/null 2>&1; then
|
||||
RT="podman"
|
||||
RT_CMD=(podman)
|
||||
COMPOSE_CMD=(podman-compose -p "$COMPOSE_PROJECT" -f "$COMPOSE_FILE")
|
||||
elif command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
|
||||
check_docker_supported
|
||||
RT="docker"
|
||||
RT_CMD=(docker)
|
||||
COMPOSE_CMD=(docker compose -p "$COMPOSE_PROJECT" -f "$COMPOSE_FILE")
|
||||
else
|
||||
runtime_install_hint
|
||||
fatal "No supported runtime found. Need podman + podman-compose or docker + docker compose."
|
||||
fi
|
||||
|
||||
info "Runtime: $RT"
|
||||
}
|
||||
|
||||
generate_compose_file() {
|
||||
local yaml_deploy
|
||||
local yaml_root
|
||||
|
||||
[[ -f "$BASE_COMPOSE_FILE" ]] || fatal "Compose file not found: $BASE_COMPOSE_FILE"
|
||||
yaml_deploy="${DEPLOY_DIR//\'/\'\'}"
|
||||
yaml_root="${ROOT_DIR//\'/\'\'}"
|
||||
|
||||
: >"$COMPOSE_FILE"
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
" context: ..")
|
||||
printf " context: '%s'\n" "$yaml_deploy" >>"$COMPOSE_FILE"
|
||||
;;
|
||||
" - /home/dalaw2/NetGuardia:/root/NetGuardia:z")
|
||||
printf " - '%s:/root/NetGuardia:z'\n" "$yaml_root" >>"$COMPOSE_FILE"
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "$line" >>"$COMPOSE_FILE"
|
||||
;;
|
||||
esac
|
||||
done <"$BASE_COMPOSE_FILE"
|
||||
}
|
||||
|
||||
run_logged() {
|
||||
local label="$1"
|
||||
shift
|
||||
|
||||
info "$label"
|
||||
if ((VERBOSE)); then
|
||||
"$@" 2>&1 | tee -a "$LOG_FILE"
|
||||
elif [[ -t 1 ]]; then
|
||||
run_with_spinner "$label" "$@"
|
||||
elif ! "$@" >>"$LOG_FILE" 2>&1; then
|
||||
warn "$label failed. Last log lines:"
|
||||
tail -n 80 "$LOG_FILE" >&2 || true
|
||||
fatal "Full log: $LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
run_with_spinner() {
|
||||
local label="$1"
|
||||
shift
|
||||
local pid status
|
||||
|
||||
"$@" >>"$LOG_FILE" 2>&1 &
|
||||
pid=$!
|
||||
spinner "$pid" "$label"
|
||||
set +e
|
||||
wait "$pid"
|
||||
status=$?
|
||||
set -e
|
||||
clear_spinner_line
|
||||
if ((status != 0)); then
|
||||
warn "$label failed. Last log lines:"
|
||||
tail -n 80 "$LOG_FILE" >&2 || true
|
||||
fatal "Full log: $LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
spinner() {
|
||||
local pid="$1"
|
||||
local label="$2"
|
||||
local frames='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
|
||||
local i=0
|
||||
local frame
|
||||
local started_at=$SECONDS
|
||||
|
||||
while kill -0 "$pid" 2>/dev/null; do
|
||||
frame="${frames:i++%${#frames}:1}"
|
||||
printf '\r%s %s... %02ds' "$frame" "$label" "$((SECONDS - started_at))"
|
||||
sleep 0.12
|
||||
done
|
||||
}
|
||||
|
||||
clear_spinner_line() {
|
||||
printf '\r\033[K'
|
||||
}
|
||||
|
||||
runtime_rm_containers() {
|
||||
"${RT_CMD[@]}" rm -f netguardia router external internal >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
delete_host_link() {
|
||||
local link_name="$1"
|
||||
ip link del "$link_name" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
preflight_cleanup() {
|
||||
((CLEANUP_FIRST)) || return 0
|
||||
|
||||
info "Cleaning old development topology"
|
||||
runtime_rm_containers
|
||||
cleanup_netns_links
|
||||
delete_host_link ext-eth0
|
||||
delete_host_link rtr-ext
|
||||
delete_host_link rtr-int
|
||||
delete_host_link ng-ext
|
||||
delete_host_link int-eth0
|
||||
delete_host_link ng-int
|
||||
}
|
||||
|
||||
container_pid() {
|
||||
"${RT_CMD[@]}" inspect --format '{{.State.Pid}}' "$1"
|
||||
}
|
||||
|
||||
link_netns() {
|
||||
local container="$1"
|
||||
local pid
|
||||
|
||||
pid="$(container_pid "$container")"
|
||||
[[ -n "$pid" && "$pid" != "0" ]] || fatal "Container '$container' is not running."
|
||||
ln -sf "/proc/$pid/ns/net" "/var/run/netns/$container"
|
||||
}
|
||||
|
||||
link_container_namespaces() {
|
||||
mkdir -p /var/run/netns
|
||||
link_netns external
|
||||
link_netns internal
|
||||
link_netns router
|
||||
link_netns netguardia
|
||||
}
|
||||
|
||||
netns() {
|
||||
ip netns exec "$@"
|
||||
}
|
||||
|
||||
disable_offload() {
|
||||
local namespace="$1"
|
||||
local interface="$2"
|
||||
|
||||
netns "$namespace" ethtool -K "$interface" tx off rx off >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
create_topology() {
|
||||
info "Creating inline veth topology"
|
||||
|
||||
ip link add ext-eth0 type veth peer name rtr-ext
|
||||
ip link set ext-eth0 netns external
|
||||
ip link set rtr-ext netns router
|
||||
|
||||
netns external ip link set lo up
|
||||
netns external ip link set ext-eth0 up
|
||||
netns external ip addr add 10.10.1.2/24 dev ext-eth0
|
||||
for i in 3 4 5 6 7; do
|
||||
netns external ip addr add "10.10.1.$i/24" dev ext-eth0
|
||||
done
|
||||
netns external ip route replace default via 10.10.1.1
|
||||
|
||||
netns router ip link set lo up
|
||||
netns router ip link set rtr-ext up
|
||||
netns router ip addr add 10.10.1.1/24 dev rtr-ext
|
||||
|
||||
ip link add rtr-int type veth peer name ng-ext
|
||||
ip link set rtr-int netns router
|
||||
ip link set ng-ext netns netguardia
|
||||
|
||||
ip link add int-eth0 type veth peer name ng-int
|
||||
ip link set int-eth0 netns internal
|
||||
ip link set ng-int netns netguardia
|
||||
|
||||
netns router ip link set rtr-int up
|
||||
netns router ip addr add 10.10.2.1/24 dev rtr-int
|
||||
netns router sh -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'
|
||||
|
||||
netns internal ip link set lo up
|
||||
netns internal ip link set int-eth0 up
|
||||
netns internal ip addr add 10.10.2.2/24 dev int-eth0
|
||||
for i in 3 4 5 6; do
|
||||
netns internal ip addr add "10.10.2.$i/24" dev int-eth0
|
||||
done
|
||||
netns internal ip route replace default via 10.10.2.1
|
||||
|
||||
netns netguardia ip link set ng-ext up
|
||||
netns netguardia ip link set ng-int up
|
||||
|
||||
disable_offload router rtr-int
|
||||
disable_offload router rtr-ext
|
||||
disable_offload internal int-eth0
|
||||
disable_offload external ext-eth0
|
||||
disable_offload netguardia ng-ext
|
||||
disable_offload netguardia ng-int
|
||||
}
|
||||
|
||||
write_interface_mapping() {
|
||||
cat >/tmp/netguardia_interfaces.txt <<'IEOF'
|
||||
# NetGuardia interface mapping - realistic inline deployment
|
||||
# Router handles L3 (10.10.1.0/24 <-> 10.10.2.0/24)
|
||||
# NetGuardia inline on 10.10.2.0/24 (no IP, no bridge)
|
||||
@ -149,30 +468,54 @@ cat > /tmp/netguardia_interfaces.txt << IEOF
|
||||
# XSK forwards packets: ng-ext RX -> ng-int TX and ng-int RX -> ng-ext TX
|
||||
# Management: eth0 (10.10.3.10)
|
||||
IEOF
|
||||
$RT cp /tmp/netguardia_interfaces.txt netguardia:/root/NetGuardia/interfaces.txt 2>/dev/null || true
|
||||
"${RT_CMD[@]}" cp /tmp/netguardia_interfaces.txt netguardia:/root/NetGuardia/interfaces.txt >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
rm -f /var/run/netns/external /var/run/netns/internal /var/run/netns/router /var/run/netns/netguardia
|
||||
connectivity_check() {
|
||||
local external_router="FAIL"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " NetGuardia realistic inline deployment!"
|
||||
echo ""
|
||||
echo " external (10.10.1.{2-7})"
|
||||
echo " |"
|
||||
echo " [router] 10.10.1.1 <-> 10.10.2.1"
|
||||
echo " | rtr-int"
|
||||
echo " |"
|
||||
echo " ng-ext (no IP) <- XDP ingress"
|
||||
echo " |"
|
||||
echo " [net-guardia XSK]"
|
||||
echo " |"
|
||||
echo " ng-int (no IP) <- XDP egress"
|
||||
echo " |"
|
||||
echo " | int-eth0"
|
||||
echo " internal (10.10.2.{2-6})"
|
||||
echo ""
|
||||
echo " All 10.10.2.0/24 traffic requires net-guardia!"
|
||||
echo " Mgmt: 10.10.3.10"
|
||||
echo " SSH: ssh -p 2222 root@<host-ip>"
|
||||
echo " Web: http://<host-ip>:8080"
|
||||
echo "=========================================="
|
||||
if netns external ping -c 1 -W 2 10.10.1.1 >/dev/null 2>&1; then
|
||||
external_router="OK"
|
||||
fi
|
||||
|
||||
info "Connectivity: external -> router: $external_router"
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
cat <<EOF
|
||||
|
||||
NetGuardia development topology is ready.
|
||||
|
||||
Mgmt: http://<host-ip>:8080
|
||||
|
||||
external (10.10.1.{2-7}) -> router -> ng-ext
|
||||
ng-ext <-> net-guardia XSK <-> ng-int
|
||||
ng-int -> internal (10.10.2.{2-6})
|
||||
EOF
|
||||
}
|
||||
|
||||
main() {
|
||||
parse_args "$@"
|
||||
require_root
|
||||
require_linux_host
|
||||
check_host_tools
|
||||
generate_compose_file
|
||||
detect_runtime
|
||||
preflight_cleanup
|
||||
|
||||
: >"$LOG_FILE"
|
||||
info "Compose log: $LOG_FILE"
|
||||
run_logged "Building containers" "${COMPOSE_CMD[@]}" build
|
||||
run_logged "Starting containers" "${COMPOSE_CMD[@]}" up -d
|
||||
|
||||
info "Containers running"
|
||||
"${RT_CMD[@]}" ps --format "table {{.Names}}\t{{.Status}}" 2>/dev/null || "${RT_CMD[@]}" ps
|
||||
|
||||
link_container_namespaces
|
||||
create_topology
|
||||
write_interface_mapping
|
||||
connectivity_check
|
||||
print_summary
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@ -1,315 +1,585 @@
|
||||
#!/bin/bash
|
||||
# NetGuardia 功能測試腳本
|
||||
# 測試:XDP 封包轉發、Web API、ML 引擎
|
||||
# 使用 graceful shutdown,所有操作設有 timeout
|
||||
#!/usr/bin/env bash
|
||||
# NetGuardia eBPF end-to-end gate.
|
||||
# Builds the dev topology with deploy/scripts/dev.sh, starts net-guardia,
|
||||
# exercises real packet paths, and shuts the data plane down with SIGINT.
|
||||
|
||||
set -uo pipefail
|
||||
set -Eeuo pipefail
|
||||
|
||||
TIMEOUT=10
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
DEV_SCRIPT="$ROOT_DIR/deploy/scripts/dev.sh"
|
||||
NG_API="http://10.10.3.10:8080"
|
||||
ADMIN_USER="admin"
|
||||
ADMIN_PASSWORD="E2eAdmin20040421!"
|
||||
CSRF_TOKEN="e2e"
|
||||
RUN_LOG="${RUN_LOG:-/tmp/netguardia-ebpf-e2e.log}"
|
||||
START_TIMEOUT_SECS="${START_TIMEOUT_SECS:-240}"
|
||||
POLL_INTERVAL_SECS=2
|
||||
|
||||
SUDO_PASSWORD=""
|
||||
RUNTIME=""
|
||||
NG_PID=""
|
||||
TOKEN=""
|
||||
PASS=0
|
||||
FAIL=0
|
||||
TESTS=()
|
||||
declare -a FAILURES=()
|
||||
|
||||
read -rsp "sudo password: " SUDO_PASSWORD
|
||||
echo
|
||||
|
||||
run_sudo() {
|
||||
sudo "$@" 2>/dev/null
|
||||
printf '%s\n' "$SUDO_PASSWORD" | sudo -S -p '' "$@"
|
||||
}
|
||||
|
||||
detect_runtime() {
|
||||
if run_sudo podman container exists netguardia >/dev/null 2>&1; then
|
||||
RUNTIME="podman"
|
||||
elif run_sudo docker container inspect netguardia >/dev/null 2>&1; then
|
||||
RUNTIME="docker"
|
||||
elif command -v podman >/dev/null 2>&1; then
|
||||
RUNTIME="podman"
|
||||
elif command -v docker >/dev/null 2>&1; then
|
||||
RUNTIME="docker"
|
||||
else
|
||||
echo "No supported container runtime found" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
container_exec() {
|
||||
run_sudo "$RUNTIME" exec "$@"
|
||||
}
|
||||
|
||||
ng_exec() {
|
||||
run_sudo podman exec netguardia bash -c "$1"
|
||||
container_exec netguardia bash -lc "$1"
|
||||
}
|
||||
|
||||
external_exec() {
|
||||
container_exec external bash -lc "$1"
|
||||
}
|
||||
|
||||
internal_exec() {
|
||||
container_exec internal bash -lc "$1"
|
||||
}
|
||||
|
||||
router_exec() {
|
||||
run_sudo podman exec router bash -c "$1"
|
||||
container_exec router bash -lc "$1"
|
||||
}
|
||||
|
||||
ext_exec() {
|
||||
run_sudo podman exec external bash -c "$1"
|
||||
container_pid() {
|
||||
run_sudo "$RUNTIME" inspect --format '{{.State.Pid}}' "$1"
|
||||
}
|
||||
|
||||
int_exec() {
|
||||
run_sudo podman exec internal bash -c "$1"
|
||||
host_netns_exec() {
|
||||
local container="$1"
|
||||
shift
|
||||
local pid
|
||||
pid="$(container_pid "$container")"
|
||||
run_sudo nsenter -t "$pid" -n "$@"
|
||||
}
|
||||
|
||||
test_result() {
|
||||
pass() {
|
||||
echo " PASS $1"
|
||||
PASS=$((PASS + 1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo " FAIL $1"
|
||||
FAIL=$((FAIL + 1))
|
||||
FAILURES+=("$1")
|
||||
}
|
||||
|
||||
check() {
|
||||
local name="$1"
|
||||
local result="$2"
|
||||
if [ "$result" -eq 0 ]; then
|
||||
echo " ✅ $name"
|
||||
PASS=$((PASS + 1))
|
||||
shift
|
||||
if "$@"; then
|
||||
pass "$name"
|
||||
else
|
||||
echo " ❌ $name"
|
||||
FAIL=$((FAIL + 1))
|
||||
fail "$name"
|
||||
fi
|
||||
TESTS+=("$name:$result")
|
||||
}
|
||||
|
||||
api_raw() {
|
||||
local method="$1"
|
||||
local path="$2"
|
||||
local body="${3:-}"
|
||||
local auth_args=()
|
||||
|
||||
if [[ -n "$TOKEN" ]]; then
|
||||
auth_args=(-H "Authorization: Bearer $TOKEN")
|
||||
case "$method" in
|
||||
POST|PUT|DELETE|PATCH)
|
||||
auth_args+=(-H "X-CSRF-Token: $CSRF_TOKEN")
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [[ -n "$body" ]]; then
|
||||
ng_exec "curl -fsS --max-time 20 -X '$method' '${NG_API}${path}' -H 'Content-Type: application/json' ${auth_args[*]@Q} -d '$body'"
|
||||
else
|
||||
ng_exec "curl -fsS --max-time 20 -X '$method' '${NG_API}${path}' ${auth_args[*]@Q}"
|
||||
fi
|
||||
}
|
||||
|
||||
api_expect_ok() {
|
||||
local method="$1"
|
||||
local path="$2"
|
||||
local body="${3:-}"
|
||||
api_raw "$method" "$path" "$body" >/dev/null
|
||||
}
|
||||
|
||||
api_ignore() {
|
||||
api_expect_ok "$@" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
json_field() {
|
||||
local field="$1"
|
||||
python3 -c 'import json,sys; data=json.load(sys.stdin); print(data.get(sys.argv[1], ""))' "$field"
|
||||
}
|
||||
|
||||
drop_counter() {
|
||||
local field="$1"
|
||||
api_raw GET /api/stats/drops | json_field "$field"
|
||||
}
|
||||
|
||||
counter_increased() {
|
||||
local field="$1"
|
||||
local before="$2"
|
||||
local after
|
||||
after="$(drop_counter "$field")"
|
||||
[[ "$after" =~ ^[0-9]+$ ]] && (( after > before ))
|
||||
}
|
||||
|
||||
counter_unchanged() {
|
||||
local field="$1"
|
||||
local before="$2"
|
||||
local after
|
||||
after="$(drop_counter "$field")"
|
||||
[[ "$after" =~ ^[0-9]+$ ]] && (( after == before ))
|
||||
}
|
||||
|
||||
wait_for_log() {
|
||||
local pattern="$1"
|
||||
local deadline=$((SECONDS + START_TIMEOUT_SECS))
|
||||
while (( SECONDS < deadline )); do
|
||||
if grep -q "$pattern" "$RUN_LOG" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
sleep "$POLL_INTERVAL_SECS"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_for_http() {
|
||||
local deadline=$((SECONDS + START_TIMEOUT_SECS))
|
||||
while (( SECONDS < deadline )); do
|
||||
if ng_exec "curl -fsS --max-time 2 '${NG_API}/api/setup/status' >/dev/null"; then
|
||||
return 0
|
||||
fi
|
||||
sleep "$POLL_INTERVAL_SECS"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
find_net_guardia_pids() {
|
||||
# shellcheck disable=SC2016 # The script is evaluated inside the container.
|
||||
ng_exec 'for p in /proc/[0-9]*/cmdline; do
|
||||
cmd=$(tr "\0" " " < "$p" 2>/dev/null || true)
|
||||
case "$cmd" in
|
||||
"target/release/net-guardia "*|"target/release/net-guardia")
|
||||
pid=${p#/proc/}; echo "${pid%/cmdline}"
|
||||
;;
|
||||
esac
|
||||
done'
|
||||
}
|
||||
|
||||
kill_existing_net_guardia() {
|
||||
local pids
|
||||
pids="$(find_net_guardia_pids || true)"
|
||||
if [[ -n "$pids" ]]; then
|
||||
ng_exec "kill -INT $pids || true"
|
||||
sleep 3
|
||||
fi
|
||||
}
|
||||
|
||||
detach_xdp_links() {
|
||||
ng_exec "ip link set dev ng-ext xdp off 2>/dev/null || true; ip link set dev ng-int xdp off 2>/dev/null || true"
|
||||
}
|
||||
|
||||
remove_pinned_xsk_maps() {
|
||||
ng_exec "rm -f /sys/fs/bpf/INGRESS_XSKS_MAP /sys/fs/bpf/EGRESS_XSKS_MAP"
|
||||
}
|
||||
|
||||
cleanup_runtime_state() {
|
||||
run_sudo rm -f "$ROOT_DIR"/net-guardia.db "$ROOT_DIR"/net-guardia.db-shm "$ROOT_DIR"/net-guardia.db-wal
|
||||
remove_pinned_xsk_maps || true
|
||||
}
|
||||
|
||||
cleanup_api_state() {
|
||||
[[ -z "$TOKEN" ]] && return 0
|
||||
api_ignore DELETE /api/acl/ipv4/source/blacklist '"10.10.1.5:0"'
|
||||
api_ignore DELETE /api/acl/ipv4/source/whitelist '"10.10.1.5:0"'
|
||||
api_ignore DELETE /api/acl/ipv6/source/blacklist '"[fd00:1::5]:0"'
|
||||
api_ignore DELETE /api/filter/http/ipv4 '["10.10.2.2:80",["GET"]]'
|
||||
api_ignore DELETE /api/filter/ssh/ipv4 '"10.10.2.2:22"'
|
||||
api_ignore DELETE /api/filter/ssh/blacklist/ipv4 '"10.10.1.5"'
|
||||
api_ignore DELETE /api/filter/ssh/whitelist/ipv4 '"10.10.1.2"'
|
||||
api_ignore POST /api/filter/ssh/whitelist/disable
|
||||
api_ignore DELETE /api/filter/dns/blacklist '{"domains":["evil.example.com"]}'
|
||||
api_ignore DELETE /api/acl/geo/unblock '{"country_codes":["US","AU","DE","NL","GB","JP","TW"]}'
|
||||
router_exec "for ip in 8.8.8.8 8.8.4.4 1.1.1.1 9.9.9.9 80.249.99.148 51.140.0.1 133.242.0.1 1.34.0.1; do ip addr del \"\$ip/32\" dev rtr-int 2>/dev/null || true; done" || true
|
||||
api_ignore PUT /api/rate-limit/config '{"packet_rate":10000,"syn_rate":100,"udp_rate":5000,"dns_rate":200,"window_ns":1000000000}'
|
||||
}
|
||||
|
||||
shutdown_net_guardia() {
|
||||
local pids
|
||||
local had_inner_process=0
|
||||
pids="$(find_net_guardia_pids || true)"
|
||||
if [[ -n "$pids" ]]; then
|
||||
had_inner_process=1
|
||||
ng_exec "kill -INT $pids || true"
|
||||
local deadline=$((SECONDS + 30))
|
||||
while (( SECONDS < deadline )); do
|
||||
[[ -z "$(find_net_guardia_pids || true)" ]] && break
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
if [[ -n "$NG_PID" ]]; then
|
||||
if kill -0 "$NG_PID" 2>/dev/null; then
|
||||
kill -INT "$NG_PID" 2>/dev/null || true
|
||||
local host_deadline=$((SECONDS + 15))
|
||||
while (( SECONDS < host_deadline )); do
|
||||
kill -0 "$NG_PID" 2>/dev/null || break
|
||||
sleep 1
|
||||
done
|
||||
if kill -0 "$NG_PID" 2>/dev/null \
|
||||
&& (( had_inner_process == 0 )) \
|
||||
&& ! ng_exec "bpftool net show | grep -Eq 'ng-ext|ng-int|net_guardia'"
|
||||
then
|
||||
kill -TERM "$NG_PID" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
if kill -0 "$NG_PID" 2>/dev/null; then
|
||||
echo " WARN net-guardia host runner still alive after SIGINT; leaving final failure to cleanup checks" >&2
|
||||
else
|
||||
wait "$NG_PID" 2>/dev/null || true
|
||||
fi
|
||||
NG_PID=""
|
||||
fi
|
||||
}
|
||||
|
||||
assert_clean_shutdown() {
|
||||
local pids
|
||||
pids="$(find_net_guardia_pids || true)"
|
||||
[[ -z "$pids" ]] || return 1
|
||||
! ng_exec "bpftool net show | grep -Eq 'ng-ext|ng-int|net_guardia|xdp.*id'"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
set +e
|
||||
cleanup_api_state
|
||||
shutdown_net_guardia
|
||||
assert_clean_shutdown >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
setup_ipv6_topology() {
|
||||
external_exec "ip -6 addr add fd00:1::2/64 dev ext-eth0 2>/dev/null || true; ip -6 addr add fd00:1::5/64 dev ext-eth0 2>/dev/null || true; ip -6 route replace default via fd00:1::1"
|
||||
router_exec "ip -6 addr add fd00:1::1/64 dev rtr-ext 2>/dev/null || true; ip -6 addr add fd00:2::1/64 dev rtr-int 2>/dev/null || true"
|
||||
host_netns_exec router sh -c "echo 1 > /proc/sys/net/ipv6/conf/all/forwarding"
|
||||
internal_exec "ip -6 addr add fd00:2::2/64 dev int-eth0 2>/dev/null || true; ip -6 route replace default via fd00:2::1"
|
||||
}
|
||||
|
||||
start_internal_http() {
|
||||
internal_exec "ssh-keygen -A >/dev/null 2>&1 || true; /usr/sbin/sshd 2>/dev/null || true; pkill -f 'python3 -m http.server 80' 2>/dev/null || true; cd /var/www/html && nohup python3 -m http.server 80 >/tmp/ng-http.log 2>&1 &"
|
||||
}
|
||||
|
||||
start_net_guardia() {
|
||||
: > "$RUN_LOG"
|
||||
(
|
||||
cd "$ROOT_DIR"
|
||||
printf '%s\n' "$SUDO_PASSWORD" | sudo -S -p '' "$RUNTIME" exec -i netguardia cargo run --release --bin net-guardia
|
||||
) >"$RUN_LOG" 2>&1 &
|
||||
NG_PID=$!
|
||||
}
|
||||
|
||||
complete_setup_if_needed() {
|
||||
local status
|
||||
status="$(ng_exec "curl -fsS --max-time 5 '${NG_API}/api/setup/status'")"
|
||||
if grep -q '"setup_complete":true' <<<"$status"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
api_raw POST /api/setup/complete '{"ingress_interface":"ng-ext","egress_interface":"ng-int","admin_password":"'"$ADMIN_PASSWORD"'","http_port":8080}' >/dev/null
|
||||
wait_for_log "Full system initialization complete"
|
||||
}
|
||||
|
||||
login() {
|
||||
local body token
|
||||
body="$(api_raw POST /api/auth/login '{"username":"'"$ADMIN_USER"'","password":"'"$ADMIN_PASSWORD"'"}')"
|
||||
token="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])' <<<"$body")"
|
||||
[[ -n "$token" ]]
|
||||
TOKEN="$token"
|
||||
}
|
||||
|
||||
scapy_send_ipv4_options_tcp() {
|
||||
external_exec "python3 - <<'PY'
|
||||
from scapy.all import IP, TCP, Raw, send, conf
|
||||
conf.verb = 0
|
||||
pkt = IP(src='10.10.1.2', dst='10.10.2.2', options=b'\x01\x01\x00\x00')/TCP(sport=45678, dport=80, flags='PA')/Raw(b'GET / HTTP/1.0\r\n\r\n')
|
||||
send(pkt, count=3, inter=0.05)
|
||||
PY"
|
||||
}
|
||||
|
||||
scapy_send_invalid_ipv4() {
|
||||
internal_exec "python3 - <<'PY'
|
||||
from scapy.all import Ether, sendp, conf
|
||||
conf.verb = 0
|
||||
# Ethernet + IPv4 version/IHL byte with invalid IHL=4, sent directly into ng-int.
|
||||
pkt = Ether(type=0x0800) / bytes([0x44,0,0,20,0,0,0,0,64,6,0,0,10,10,1,2,10,10,2,2])
|
||||
sendp(pkt, iface='int-eth0', count=3, inter=0.05)
|
||||
PY"
|
||||
}
|
||||
|
||||
tcp_connect_from_external() {
|
||||
local src_ip="$1"
|
||||
local dst="$2"
|
||||
external_exec "timeout 5 curl -fsS --interface '$src_ip' '$dst' >/dev/null"
|
||||
}
|
||||
|
||||
ping4_from_external() {
|
||||
local src_ip="$1"
|
||||
local dst_ip="$2"
|
||||
external_exec "ping -c 2 -W 3 -I '$src_ip' '$dst_ip' >/dev/null 2>&1"
|
||||
}
|
||||
|
||||
ping6_from_external() {
|
||||
local src_ip="$1"
|
||||
local dst_ip="$2"
|
||||
external_exec "ping -6 -c 2 -W 3 -I '$src_ip' '$dst_ip' >/dev/null 2>&1"
|
||||
}
|
||||
|
||||
expect_blocked() {
|
||||
if "$@"; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
send_burst() {
|
||||
local kind="$1"
|
||||
external_exec "python3 - '$kind' <<'PY'
|
||||
import sys
|
||||
from scapy.all import IP, ICMP, TCP, UDP, DNS, DNSQR, send, conf
|
||||
conf.verb = 0
|
||||
kind = sys.argv[1]
|
||||
if kind == 'packet':
|
||||
pkt = IP(src='10.10.1.5', dst='10.10.2.2')/ICMP()
|
||||
elif kind == 'syn':
|
||||
pkt = IP(src='10.10.1.5', dst='10.10.2.2')/TCP(sport=41000, dport=22, flags='S')
|
||||
elif kind == 'udp':
|
||||
pkt = IP(src='10.10.1.5', dst='10.10.2.2')/UDP(sport=41000, dport=9999)/b'x'
|
||||
elif kind == 'dns':
|
||||
pkt = IP(src='10.10.1.5', dst='10.10.2.2')/UDP(sport=41000, dport=53)/DNS(rd=1, qd=DNSQR(qname='rate.example.com'))
|
||||
else:
|
||||
raise SystemExit(2)
|
||||
send(pkt, count=20, inter=0.01)
|
||||
PY"
|
||||
}
|
||||
|
||||
send_dns_query() {
|
||||
local domain="$1"
|
||||
external_exec "python3 - '$domain' <<'PY'
|
||||
import sys
|
||||
from scapy.all import IP, UDP, DNS, DNSQR, send, conf
|
||||
conf.verb = 0
|
||||
domain = sys.argv[1]
|
||||
pkt = IP(src='10.10.1.5', dst='10.10.2.2')/UDP(sport=53000, dport=53)/DNS(rd=1, qd=DNSQR(qname=domain))
|
||||
send(pkt, count=5, inter=0.05)
|
||||
PY"
|
||||
}
|
||||
|
||||
send_geo_packet() {
|
||||
local source_ip="$1"
|
||||
router_exec "ip addr add '$source_ip/32' dev rtr-int 2>/dev/null || true; ping -c 5 -W 1 -I '$source_ip' 10.10.2.2 >/dev/null 2>&1 || true"
|
||||
}
|
||||
|
||||
geo_block_prefixes() {
|
||||
local country="$1"
|
||||
local response
|
||||
response="$(api_raw PUT /api/acl/geo/block '{"country_codes":["'"$country"'"]}')"
|
||||
python3 -c 'import json,sys; print(json.load(sys.stdin).get("total_prefixes", 0))' <<<"$response"
|
||||
}
|
||||
|
||||
geo_unblock_country() {
|
||||
local country="$1"
|
||||
api_expect_ok DELETE /api/acl/geo/unblock '{"country_codes":["'"$country"'"]}'
|
||||
}
|
||||
|
||||
geo_block_counter_increases() {
|
||||
local candidate country source_ip prefixes before
|
||||
local candidates=(
|
||||
"US 8.8.8.8"
|
||||
"US 8.8.4.4"
|
||||
"AU 1.1.1.1"
|
||||
"DE 9.9.9.9"
|
||||
"NL 80.249.99.148"
|
||||
"GB 51.140.0.1"
|
||||
"JP 133.242.0.1"
|
||||
"TW 1.34.0.1"
|
||||
)
|
||||
|
||||
for candidate in "${candidates[@]}"; do
|
||||
country="${candidate%% *}"
|
||||
source_ip="${candidate#* }"
|
||||
geo_unblock_country "$country" >/dev/null 2>&1 || true
|
||||
if ! prefixes="$(geo_block_prefixes "$country" 2>/dev/null)"; then
|
||||
geo_unblock_country "$country" >/dev/null 2>&1 || true
|
||||
continue
|
||||
fi
|
||||
if ! [[ "$prefixes" =~ ^[0-9]+$ ]] || (( prefixes == 0 )); then
|
||||
geo_unblock_country "$country" >/dev/null 2>&1 || true
|
||||
continue
|
||||
fi
|
||||
|
||||
before="$(drop_counter geo_block)"
|
||||
send_geo_packet "$source_ip"
|
||||
sleep 1
|
||||
if counter_increased geo_block "$before"; then
|
||||
geo_unblock_country "$country" >/dev/null 2>&1 || true
|
||||
return 0
|
||||
fi
|
||||
geo_unblock_country "$country" >/dev/null 2>&1 || true
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
run_rate_limit_case() {
|
||||
local name="$1"
|
||||
local field="$2"
|
||||
local config="$3"
|
||||
local burst_kind="$4"
|
||||
local before
|
||||
before="$(drop_counter "$field")"
|
||||
api_expect_ok PUT /api/rate-limit/config "$config"
|
||||
sleep 1
|
||||
send_burst "$burst_kind"
|
||||
sleep 1
|
||||
check "$name" counter_increased "$field" "$before"
|
||||
api_expect_ok PUT /api/rate-limit/config '{"packet_rate":10000,"syn_rate":100,"udp_rate":5000,"dns_rate":200,"window_ns":1000000000}'
|
||||
}
|
||||
|
||||
echo "=========================================="
|
||||
echo " NetGuardia 功能測試"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# 1. 基礎檢查
|
||||
# ============================================================
|
||||
echo "--- 1. 基礎檢查 ---"
|
||||
|
||||
# 1a. 容器運行中
|
||||
run_sudo podman ps --filter name=netguardia --format "{{.Status}}" | grep -q "Up" 2>/dev/null
|
||||
test_result "netguardia 容器運行中" $?
|
||||
|
||||
# 1b. XDP 程式已附加
|
||||
ng_exec "ip link show ng-ext 2>/dev/null | grep -q xdp"
|
||||
test_result "ng-ext XDP 程式已附加" $?
|
||||
|
||||
ng_exec "ip link show ng-int 2>/dev/null | grep -q xdp"
|
||||
test_result "ng-int XDP 程式已附加" $?
|
||||
|
||||
# 1c. 管理網路可達
|
||||
ng_exec "ping -c1 -W $TIMEOUT 10.10.3.1 >/dev/null 2>&1"
|
||||
test_result "管理網路 (10.10.3.1) 可達" $?
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# 2. XDP 封包轉發測試
|
||||
# ============================================================
|
||||
echo "--- 2. XDP 封包轉發測試 ---"
|
||||
|
||||
# 2a. Router -> Internal (透過 NetGuardia)
|
||||
router_exec "ping -c 3 -W $TIMEOUT 10.10.2.2 >/dev/null 2>&1"
|
||||
test_result "Router → Internal ICMP 轉發 (10.10.2.2)" $?
|
||||
|
||||
# 2b. Internal -> Router (反向轉發)
|
||||
int_exec "ping -c 3 -W $TIMEOUT 10.10.2.1 >/dev/null 2>&1"
|
||||
test_result "Internal → Router ICMP 轉發 (10.10.2.1)" $?
|
||||
|
||||
# 2c. External -> Internal (全路徑: external -> router -> netguardia -> internal)
|
||||
ext_exec "ping -c 3 -W $TIMEOUT 10.10.2.2 >/dev/null 2>&1"
|
||||
test_result "External → Internal 全路徑 ICMP" $?
|
||||
|
||||
# 2d. Internal -> External (全路徑反向)
|
||||
int_exec "ping -c 3 -W $TIMEOUT 10.10.1.2 >/dev/null 2>&1"
|
||||
test_result "Internal → External 全路徑 ICMP" $?
|
||||
|
||||
# 2e. TCP 轉發 (HTTP)
|
||||
ext_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' http://10.10.2.2/ 2>/dev/null" | grep -q "200"
|
||||
test_result "External → Internal HTTP (TCP 轉發)" $?
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# 3. Web API 測試
|
||||
# ============================================================
|
||||
echo "--- 3. Web API 測試 ---"
|
||||
|
||||
# 3a. Health endpoint
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' $NG_API/api/health/status" | grep -q "200"
|
||||
test_result "GET /api/health/status" $?
|
||||
|
||||
# 3b. Health metrics
|
||||
ng_exec "timeout $TIMEOUT curl -s $NG_API/api/health/metrics" | grep -q "cpu" 2>/dev/null
|
||||
test_result "GET /api/health/metrics (含 CPU 資訊)" $?
|
||||
|
||||
# 3c. Flow stats
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' $NG_API/api/stats/flows" | grep -q "200"
|
||||
test_result "GET /api/stats/flows" $?
|
||||
|
||||
# 3e. Stats summary
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' $NG_API/api/stats/summary" | grep -q "200"
|
||||
test_result "GET /api/stats/summary" $?
|
||||
|
||||
# 3f. ML status
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' $NG_API/api/ml/status" | grep -q "200"
|
||||
test_result "GET /api/ml/status" $?
|
||||
|
||||
# 3g. ACL list
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' $NG_API/api/acl/ipv4/source/whitelist" | grep -q "200"
|
||||
test_result "GET /api/acl/ipv4/source/whitelist" $?
|
||||
|
||||
# 3h. Rate limit config
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' $NG_API/api/rate-limit/config" | grep -q "200"
|
||||
test_result "GET /api/rate-limit/config" $?
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# 4. ACL 功能測試
|
||||
# ============================================================
|
||||
echo "--- 4. ACL 功能測試 ---"
|
||||
|
||||
# 4a. 添加黑名單規則 (封鎖 10.10.1.5) — API 接收 SocketAddrV4 格式 "ip:port"
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' -X PUT '$NG_API/api/acl/ipv4/source/blacklist' -H 'Content-Type: application/json' -d '\"10.10.1.5:0\"'" | grep -q "200"
|
||||
test_result "PUT ACL 黑名單規則 (封鎖 10.10.1.5)" $?
|
||||
|
||||
# 4b. 驗證封鎖生效 (10.10.1.5 不該能 ping 到 internal)
|
||||
sleep 1
|
||||
ext_exec "ping -c 2 -W 3 -I 10.10.1.5 10.10.2.2 >/dev/null 2>&1" && BLOCKED=1 || BLOCKED=0
|
||||
test_result "10.10.1.5 被封鎖 (ping 失敗)" $BLOCKED
|
||||
|
||||
# 4c. 未封鎖的 IP 仍然可達
|
||||
ext_exec "ping -c 2 -W $TIMEOUT -I 10.10.1.2 10.10.2.2 >/dev/null 2>&1"
|
||||
test_result "10.10.1.2 未受影響 (ping 成功)" $?
|
||||
|
||||
# 4d. 移除黑名單規則
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' -X DELETE '$NG_API/api/acl/ipv4/source/blacklist' -H 'Content-Type: application/json' -d '\"10.10.1.5:0\"'" | grep -q "200"
|
||||
test_result "DELETE ACL 黑名單規則 (解除 10.10.1.5)" $?
|
||||
|
||||
# 4e. 驗證解除封鎖
|
||||
sleep 1
|
||||
ext_exec "ping -c 2 -W $TIMEOUT -I 10.10.1.5 10.10.2.2 >/dev/null 2>&1"
|
||||
test_result "10.10.1.5 解除封鎖 (ping 恢復)" $?
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# 5. Rate Limit 測試
|
||||
# ============================================================
|
||||
echo "--- 5. Rate Limit 測試 ---"
|
||||
|
||||
# 5a. 讀取當前 rate limit 設定
|
||||
RL_CONFIG=$(ng_exec "timeout $TIMEOUT curl -s $NG_API/api/rate-limit/config")
|
||||
echo "$RL_CONFIG" | grep -q "packet_rate" 2>/dev/null
|
||||
test_result "Rate limit 設定可讀取" $?
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# 6. ML 引擎測試
|
||||
# ============================================================
|
||||
echo "--- 6. ML 引擎測試 ---"
|
||||
|
||||
ML_STATUS=$(ng_exec "timeout $TIMEOUT curl -s $NG_API/api/ml/status")
|
||||
echo "$ML_STATUS" | grep -q "active\|logging\|disabled" 2>/dev/null
|
||||
test_result "ML 引擎狀態可查詢" $?
|
||||
|
||||
# 檢查 traffic log 是否在寫入 (traffic_logging_mode = true)
|
||||
ng_exec "test -f /root/NetGuardia/traffic_log.csv && wc -l < /root/NetGuardia/traffic_log.csv || echo 0" | grep -qv "^0$" 2>/dev/null
|
||||
test_result "Traffic log CSV 有寫入資料" $?
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# 7. WebSocket 測試
|
||||
# ============================================================
|
||||
echo "--- 7. WebSocket 測試 ---"
|
||||
|
||||
# 簡單測試 WS endpoint 是否回應 (upgrade request)
|
||||
# curl -sv 輸出 status code 到 stderr,101 Switching Protocols 表示成功
|
||||
WS_CODE=$(ng_exec "timeout 3 curl -s -o /dev/null -w '%{http_code}' -H 'Upgrade: websocket' -H 'Connection: Upgrade' -H 'Sec-WebSocket-Key: dGVzdA==' -H 'Sec-WebSocket-Version: 13' $NG_API/ws/health 2>/dev/null || true")
|
||||
echo "$WS_CODE" | grep -q "101"
|
||||
test_result "WebSocket /ws/health 升級成功 (101)" $?
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# 8. GeoIP 國家封鎖 API 測試
|
||||
# ============================================================
|
||||
echo "--- 8. GeoIP 國家封鎖 API 測試 ---"
|
||||
|
||||
# 8a. 查詢目前封鎖的國家列表
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' $NG_API/api/acl/geo/blocked" | grep -q "200"
|
||||
test_result "GET /api/acl/geo/blocked" $?
|
||||
|
||||
# 8b. 封鎖國家 (CN, RU) — GeoIP rebuild 需要掃描 MaxMind DB,timeout 加長到 30s
|
||||
ng_exec "timeout 30 curl -s -o /dev/null -w '%{http_code}' -X PUT '$NG_API/api/acl/geo/block' -H 'Content-Type: application/json' -d '{\"country_codes\":[\"CN\",\"RU\"]}'" | grep -q "200"
|
||||
test_result "PUT /api/acl/geo/block 封鎖 CN, RU" $?
|
||||
|
||||
# 8c. 驗證封鎖列表包含 CN
|
||||
GEO_BLOCKED=$(ng_exec "timeout $TIMEOUT curl -s $NG_API/api/acl/geo/blocked")
|
||||
echo "$GEO_BLOCKED" | grep -q "CN" 2>/dev/null
|
||||
test_result "封鎖列表包含 CN" $?
|
||||
|
||||
# 8d. 回傳包含 total_prefixes
|
||||
GEO_PUT_RESP=$(ng_exec "timeout 30 curl -s -X PUT '$NG_API/api/acl/geo/block' -H 'Content-Type: application/json' -d '{\"country_codes\":[\"KP\"]}'")
|
||||
echo "$GEO_PUT_RESP" | grep -q "total_prefixes" 2>/dev/null
|
||||
test_result "PUT 回傳 total_prefixes 欄位" $?
|
||||
|
||||
# 8e. 解除封鎖
|
||||
ng_exec "timeout 30 curl -s -o /dev/null -w '%{http_code}' -X DELETE '$NG_API/api/acl/geo/unblock' -H 'Content-Type: application/json' -d '{\"country_codes\":[\"CN\",\"RU\",\"KP\"]}'" | grep -q "200"
|
||||
test_result "DELETE /api/acl/geo/unblock 清除所有 GeoIP 規則" $?
|
||||
|
||||
# 8f. 驗證清空
|
||||
GEO_AFTER=$(ng_exec "timeout $TIMEOUT curl -s $NG_API/api/acl/geo/blocked")
|
||||
echo "$GEO_AFTER" | grep -q '"blocked_countries":\[\]' 2>/dev/null || echo "$GEO_AFTER" | grep -q '"blocked_countries": \[\]' 2>/dev/null
|
||||
test_result "封鎖列表已清空" $?
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# 9. DNS 黑名單 API 測試
|
||||
# ============================================================
|
||||
echo "--- 9. DNS 黑名單 API 測試 ---"
|
||||
|
||||
# 9a. 查詢 DNS 黑名單
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' $NG_API/api/filter/dns/blacklist" | grep -q "200"
|
||||
test_result "GET /api/filter/dns/blacklist" $?
|
||||
|
||||
# 9b. 新增域名到黑名單
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' -X PUT '$NG_API/api/filter/dns/blacklist' -H 'Content-Type: application/json' -d '{\"domains\":[\"malware.example.com\",\"phishing.test.org\"]}'" | grep -q "200"
|
||||
test_result "PUT /api/filter/dns/blacklist 新增域名" $?
|
||||
|
||||
# 9c. 驗證黑名單已更新
|
||||
DNS_DOMAINS=$(ng_exec "timeout $TIMEOUT curl -s $NG_API/api/filter/dns/blacklist")
|
||||
echo "$DNS_DOMAINS" | grep -q "malware.example.com" 2>/dev/null
|
||||
test_result "黑名單包含 malware.example.com" $?
|
||||
|
||||
# 9d. 移除域名
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' -X DELETE '$NG_API/api/filter/dns/blacklist' -H 'Content-Type: application/json' -d '{\"domains\":[\"phishing.test.org\"]}'" | grep -q "200"
|
||||
test_result "DELETE 移除 phishing.test.org" $?
|
||||
|
||||
# 9e. 驗證移除結果
|
||||
DNS_AFTER=$(ng_exec "timeout $TIMEOUT curl -s $NG_API/api/filter/dns/blacklist")
|
||||
echo "$DNS_AFTER" | grep -q "malware.example.com" 2>/dev/null
|
||||
test_result "移除後仍包含 malware.example.com" $?
|
||||
|
||||
# 9f. 清除所有 DNS 黑名單
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' -X DELETE '$NG_API/api/filter/dns/blacklist' -H 'Content-Type: application/json' -d '{\"domains\":[\"malware.example.com\"]}'" | grep -q "200"
|
||||
test_result "清除所有 DNS 黑名單規則" $?
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# 10. DNS 黑名單封鎖流量驗證
|
||||
# ============================================================
|
||||
echo "--- 10. DNS 黑名單封鎖流量驗證 ---"
|
||||
|
||||
# 10a. 新增測試域名到黑名單
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' -X PUT '$NG_API/api/filter/dns/blacklist' -H 'Content-Type: application/json' -d '{\"domains\":[\"evil.example.com\"]}'" | grep -q "200"
|
||||
test_result "新增 evil.example.com 到 DNS 黑名單" $?
|
||||
|
||||
# 10b. 對黑名單域名的 DNS 查詢應被丟棄 (timeout)
|
||||
sleep 1
|
||||
ext_exec "timeout 3 dig @10.10.2.2 evil.example.com +time=2 +tries=1 >/dev/null 2>&1" && DNS_BLOCKED=1 || DNS_BLOCKED=0
|
||||
test_result "evil.example.com DNS 查詢被封鎖" $DNS_BLOCKED
|
||||
|
||||
# 10c. 子域名也應被封鎖
|
||||
ext_exec "timeout 3 dig @10.10.2.2 sub.evil.example.com +time=2 +tries=1 >/dev/null 2>&1" && SUB_BLOCKED=1 || SUB_BLOCKED=0
|
||||
test_result "sub.evil.example.com 子域名也被封鎖" $SUB_BLOCKED
|
||||
|
||||
# 10d. 清除
|
||||
ng_exec "timeout $TIMEOUT curl -s -o /dev/null -w '%{http_code}' -X DELETE '$NG_API/api/filter/dns/blacklist' -H 'Content-Type: application/json' -d '{\"domains\":[\"evil.example.com\"]}'" | grep -q "200"
|
||||
test_result "清除 DNS 黑名單測試規則" $?
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================================
|
||||
# 結果總結
|
||||
# ============================================================
|
||||
echo "=========================================="
|
||||
echo " 測試結果: $PASS 通過 / $FAIL 失敗 / $((PASS + FAIL)) 總計"
|
||||
echo " NetGuardia eBPF E2E Gate"
|
||||
echo "=========================================="
|
||||
|
||||
if [ "$FAIL" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "失敗的測試:"
|
||||
for t in "${TESTS[@]}"; do
|
||||
name="${t%:*}"
|
||||
result="${t##*:}"
|
||||
if [ "$result" -ne 0 ]; then
|
||||
echo " ❌ $name"
|
||||
fi
|
||||
done
|
||||
echo "--- Environment setup ---"
|
||||
run_sudo "$DEV_SCRIPT"
|
||||
detect_runtime
|
||||
kill_existing_net_guardia
|
||||
detach_xdp_links
|
||||
cleanup_runtime_state
|
||||
setup_ipv6_topology
|
||||
start_internal_http
|
||||
|
||||
echo "--- Starting net-guardia ---"
|
||||
start_net_guardia
|
||||
check "setup/status becomes reachable" wait_for_http
|
||||
check "setup completion starts full system" complete_setup_if_needed
|
||||
check "ng-ext XDP attach logged" wait_for_log "XDP attached to ng-ext"
|
||||
check "ng-int XDP attach logged" wait_for_log "XDP attached to ng-int"
|
||||
check "XSK queue starts" wait_for_log "Queue pair 0 started successfully"
|
||||
check "full system initialized" wait_for_log "Full system initialization complete"
|
||||
check "login succeeds" login
|
||||
|
||||
echo "--- XSK forwarding ---"
|
||||
check "IPv4 external to internal ICMP" ping4_from_external 10.10.1.2 10.10.2.2
|
||||
check "IPv4 internal to external ICMP" internal_exec "ping -c 2 -W 3 10.10.1.2 >/dev/null 2>&1"
|
||||
check "IPv4 external to internal HTTP" tcp_connect_from_external 10.10.1.2 http://10.10.2.2/
|
||||
check "IPv6 external to internal ICMP" ping6_from_external fd00:1::2 fd00:2::2
|
||||
|
||||
echo "--- Parser packet path ---"
|
||||
check "IPv4 IHL=6 TCP packet path does not detach" scapy_send_ipv4_options_tcp
|
||||
sleep 1
|
||||
check "Invalid IPv4 packet does not detach XDP" scapy_send_invalid_ipv4
|
||||
check "XDP links remain attached after parser probes" ng_exec "bpftool net show | grep -Eq 'ng-ext|ng-int'"
|
||||
|
||||
echo "--- ACL ---"
|
||||
acl_before="$(drop_counter acl_blacklist)"
|
||||
api_expect_ok PUT /api/acl/ipv4/source/blacklist '"10.10.1.5:0"'
|
||||
check "IPv4 source blacklist drops traffic" expect_blocked ping4_from_external 10.10.1.5 10.10.2.2
|
||||
check "ACL blacklist counter increases" counter_increased acl_blacklist "$acl_before"
|
||||
api_expect_ok PUT /api/acl/ipv4/source/whitelist '"10.10.1.5:0"'
|
||||
check "Whitelist overrides blacklist" ping4_from_external 10.10.1.5 10.10.2.2
|
||||
api_expect_ok DELETE /api/acl/ipv4/source/whitelist '"10.10.1.5:0"'
|
||||
check "Blacklist resumes after whitelist removal" expect_blocked ping4_from_external 10.10.1.5 10.10.2.2
|
||||
api_expect_ok DELETE /api/acl/ipv4/source/blacklist '"10.10.1.5:0"'
|
||||
acl6_before="$(drop_counter acl_blacklist)"
|
||||
api_expect_ok PUT /api/acl/ipv6/source/blacklist '"[fd00:1::5]:0"'
|
||||
check "IPv6 source blacklist drops traffic" expect_blocked ping6_from_external fd00:1::5 fd00:2::2
|
||||
check "IPv6 ACL counter increases" counter_increased acl_blacklist "$acl6_before"
|
||||
api_expect_ok DELETE /api/acl/ipv6/source/blacklist '"[fd00:1::5]:0"'
|
||||
|
||||
echo "--- Protocol filter ---"
|
||||
proto_before="$(drop_counter protocol_filter)"
|
||||
api_expect_ok PUT /api/filter/http/ipv4 '["10.10.2.2:80",["GET"]]'
|
||||
check "HTTP GET is allowed" external_exec "timeout 5 curl -fsS -X GET http://10.10.2.2/ >/dev/null"
|
||||
check "HTTP POST is dropped" expect_blocked external_exec "timeout 5 curl -fsS -X POST http://10.10.2.2/ >/dev/null"
|
||||
check "Protocol filter counter increases for POST" counter_increased protocol_filter "$proto_before"
|
||||
api_expect_ok DELETE /api/filter/http/ipv4 '["10.10.2.2:80",["GET"]]'
|
||||
|
||||
proto_ssh_before="$(drop_counter protocol_filter)"
|
||||
api_expect_ok PUT /api/filter/ssh/ipv4 '"10.10.2.2:22"'
|
||||
api_expect_ok PUT /api/filter/ssh/blacklist/ipv4 '"10.10.1.5"'
|
||||
check "SSH blacklist drops TCP connect" expect_blocked external_exec "timeout 5 nc -z -s 10.10.1.5 10.10.2.2 22"
|
||||
check "SSH blacklist counter increases" counter_increased protocol_filter "$proto_ssh_before"
|
||||
api_expect_ok DELETE /api/filter/ssh/blacklist/ipv4 '"10.10.1.5"'
|
||||
api_expect_ok POST /api/filter/ssh/whitelist/enable
|
||||
api_expect_ok PUT /api/filter/ssh/whitelist/ipv4 '"10.10.1.2"'
|
||||
check "SSH whitelist allows listed source" external_exec "timeout 5 nc -z -s 10.10.1.2 10.10.2.2 22"
|
||||
check "SSH whitelist blocks unlisted source" expect_blocked external_exec "timeout 5 nc -z -s 10.10.1.5 10.10.2.2 22"
|
||||
api_expect_ok POST /api/filter/ssh/whitelist/disable
|
||||
api_expect_ok DELETE /api/filter/ssh/whitelist/ipv4 '"10.10.1.2"'
|
||||
api_expect_ok DELETE /api/filter/ssh/ipv4 '"10.10.2.2:22"'
|
||||
|
||||
echo "--- Rate limit ---"
|
||||
run_rate_limit_case "Packet rate limit bucket" rate_limit_pkt '{"packet_rate":2,"syn_rate":1000,"udp_rate":1000,"dns_rate":1000,"window_ns":2000000000}' packet
|
||||
run_rate_limit_case "SYN rate limit bucket" rate_limit_syn '{"packet_rate":1000,"syn_rate":2,"udp_rate":1000,"dns_rate":1000,"window_ns":2000000000}' syn
|
||||
run_rate_limit_case "UDP rate limit bucket" rate_limit_udp '{"packet_rate":1000,"syn_rate":1000,"udp_rate":2,"dns_rate":1000,"window_ns":2000000000}' udp
|
||||
run_rate_limit_case "DNS rate limit bucket" rate_limit_dns '{"packet_rate":1000,"syn_rate":1000,"udp_rate":1000,"dns_rate":2,"window_ns":2000000000}' dns
|
||||
|
||||
echo "--- DNS blacklist ---"
|
||||
dns_before="$(drop_counter dns_blacklist)"
|
||||
api_expect_ok PUT /api/filter/dns/blacklist '{"domains":["evil.example.com"]}'
|
||||
send_dns_query evil.example.com
|
||||
sleep 1
|
||||
check "DNS blacklist counter increases" counter_increased dns_blacklist "$dns_before"
|
||||
dns_allowed_before="$(drop_counter dns_blacklist)"
|
||||
send_dns_query allowed.example.com
|
||||
sleep 1
|
||||
check "Non-blacklisted DNS does not increase DNS blacklist drops" counter_unchanged dns_blacklist "$dns_allowed_before"
|
||||
api_expect_ok DELETE /api/filter/dns/blacklist '{"domains":["evil.example.com"]}'
|
||||
|
||||
echo "--- GeoIP block ---"
|
||||
check "GeoIP block counter increases" geo_block_counter_increases
|
||||
|
||||
echo "--- Graceful shutdown ---"
|
||||
cleanup_api_state
|
||||
shutdown_net_guardia
|
||||
check "No residual XDP link or net-guardia process" assert_clean_shutdown
|
||||
|
||||
trap - EXIT
|
||||
|
||||
echo "=========================================="
|
||||
echo " eBPF E2E result: $PASS passed / $FAIL failed"
|
||||
echo "=========================================="
|
||||
if (( FAIL > 0 )); then
|
||||
printf 'Failures:\n'
|
||||
printf ' - %s\n' "${FAILURES[@]}"
|
||||
echo "Run log: $RUN_LOG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit $FAIL
|
||||
echo "Run log: $RUN_LOG"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user