mirror of
https://github.com/DaLaw2/NetGuardia.git
synced 2026-08-24 14:10:28 +09:00
523 lines
14 KiB
Bash
Executable File
523 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build the NetGuardia development containers and inline veth topology.
|
|
|
|
set -Eeuo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
DEPLOY_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
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"
|
|
|
|
VERBOSE=0
|
|
CLEANUP_FIRST=1
|
|
RT=""
|
|
declare -a RT_CMD=()
|
|
declare -a COMPOSE_CMD=()
|
|
|
|
info() {
|
|
printf '[INFO] %s\n' "$*"
|
|
}
|
|
|
|
warn() {
|
|
printf '[WARN] %s\n' "$*" >&2
|
|
}
|
|
|
|
fatal() {
|
|
printf '[ERROR] %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: sudo bash deploy/scripts/dev.sh [--verbose] [--no-cleanup]
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
cleanup_netns_links() {
|
|
rm -f \
|
|
/var/run/netns/external \
|
|
/var/run/netns/internal \
|
|
/var/run/netns/router \
|
|
/var/run/netns/netguardia
|
|
}
|
|
|
|
cleanup_temp_files() {
|
|
cleanup_netns_links
|
|
rm -f "$COMPOSE_FILE"
|
|
}
|
|
|
|
trap cleanup_temp_files EXIT
|
|
|
|
require_root() {
|
|
[[ "$(id -u)" -eq 0 ]] || fatal "dev.sh must run as root. Use: sudo bash deploy/scripts/dev.sh"
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
package_for_command() {
|
|
local manager="$1"
|
|
local command_name="$2"
|
|
|
|
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
|
|
}
|
|
|
|
append_unique() {
|
|
local value="$1"
|
|
shift
|
|
local existing
|
|
|
|
for existing in "$@"; do
|
|
[[ "$existing" == "$value" ]] && return 1
|
|
done
|
|
return 0
|
|
}
|
|
|
|
install_packages() {
|
|
local manager="$1"
|
|
shift
|
|
|
|
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
|
|
}
|
|
|
|
manual_install_command() {
|
|
local manager="$1"
|
|
shift
|
|
|
|
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
|
|
}
|
|
|
|
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)
|
|
# ng-ext - XDP ingress (router side, attached to rtr-int peer)
|
|
# ng-int - XDP egress (internal side, attached to int-eth0 peer)
|
|
# XSK forwards packets: ng-ext RX -> ng-int TX and ng-int RX -> ng-ext TX
|
|
# Management: eth0 (10.10.3.10)
|
|
IEOF
|
|
"${RT_CMD[@]}" cp /tmp/netguardia_interfaces.txt netguardia:/root/NetGuardia/interfaces.txt >/dev/null 2>&1 || true
|
|
}
|
|
|
|
connectivity_check() {
|
|
local external_router="FAIL"
|
|
|
|
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
|
|
generate_compose_file
|
|
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 "$@"
|