#!/bin/bash # ============================================================================= # install-snmpsim-lab.sh # # Installs the SevOne SNMP Simulation Lab into /opt/snmpsim # All 10 devices use SNMPv2c (community: public) # # Components: # - snmpsim-lextudio : 10 faux Cisco SNMP agents # - trap-generator.sh : Random SNMP trap sender # - nflow-generator : Synthetic NetFlow v5 exporter # # Usage: # ./install-snmpsim-lab.sh [OPTIONS] # # Options: # -v, --vm-ip IP address of this VM (SNMP agents bind to this IP) # -t, --trap-host IP address of the SNMP trap destination (SevOne) # -n, --netflow-host IP address of the NetFlow collector (SevOne) # -p, --netflow-port UDP port for NetFlow (default: 9996) # -T, --trap-port UDP port for SNMP traps (default: 162) # -h, --help Show this help message # # Example: # ./install-snmpsim-lab.sh \ # --vm-ip 9.60.154.118 \ # --trap-host 9.60.151.170 \ # --netflow-host 9.60.151.170 # # ============================================================================= set -euo pipefail # ── Defaults ───────────────────────────────────────────────────────────────── INSTALL_DIR="/opt/snmpsim" SNMPSIM_USER="snmpsim" NETFLOW_PORT="9996" TRAP_PORT="162" VM_IP="" TRAP_HOST="" NETFLOW_HOST="" # ── Colours for output ──────────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # No Colour info() { echo -e "${CYAN}[INFO]${NC} $*"; } ok() { echo -e "${GREEN}[OK]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } section() { echo -e "\n${BLUE}══════════════════════════════════════════${NC}"; \ echo -e "${BLUE} $*${NC}"; \ echo -e "${BLUE}══════════════════════════════════════════${NC}"; } # ── Usage ───────────────────────────────────────────────────────────────────── usage() { cat << EOF Usage: $(basename "$0") [OPTIONS] OPTIONS: -v, --vm-ip IP address of this VM that SNMP agents will bind to [REQUIRED] -t, --trap-host Destination IP for SNMP traps (SevOne) [REQUIRED] -n, --netflow-host Destination IP for NetFlow (SevOne) [REQUIRED] -p, --netflow-port UDP port for NetFlow collector (default: 9996) -T, --trap-port UDP port for SNMP trap receiver (default: 162) -h, --help Show this help EXAMPLE: $(basename "$0") \\ --vm-ip 9.60.154.118 \\ --trap-host 9.60.151.170 \\ --netflow-host 9.60.151.170 EOF exit 0 } # ── Parse arguments ─────────────────────────────────────────────────────────── if [[ $# -eq 0 ]]; then usage; fi while [[ $# -gt 0 ]]; do case "$1" in -v|--vm-ip) VM_IP="$2"; shift 2 ;; -t|--trap-host) TRAP_HOST="$2"; shift 2 ;; -n|--netflow-host) NETFLOW_HOST="$2"; shift 2 ;; -p|--netflow-port) NETFLOW_PORT="$2"; shift 2 ;; -T|--trap-port) TRAP_PORT="$2"; shift 2 ;; -h|--help) usage ;; *) error "Unknown option: $1"; usage ;; esac done # ── Validate required arguments ─────────────────────────────────────────────── MISSING=0 [[ -z "$VM_IP" ]] && { error "--vm-ip is required"; MISSING=1; } [[ -z "$TRAP_HOST" ]] && { error "--trap-host is required"; MISSING=1; } [[ -z "$NETFLOW_HOST" ]] && { error "--netflow-host is required"; MISSING=1; } [[ $MISSING -eq 1 ]] && { echo ""; usage; } # ── Must run as root ────────────────────────────────────────────────────────── if [[ $EUID -ne 0 ]]; then error "This script must be run as root (sudo)" exit 1 fi # ── Summary ─────────────────────────────────────────────────────────────────── echo "" echo -e "${BLUE}╔══════════════════════════════════════════════════╗${NC}" echo -e "${BLUE}║ SevOne SNMP Lab Installer ║${NC}" echo -e "${BLUE}╚══════════════════════════════════════════════════╝${NC}" echo "" echo -e " Install directory : ${CYAN}${INSTALL_DIR}${NC}" echo -e " VM IP (SNMP bind) : ${CYAN}${VM_IP}${NC}" echo -e " Trap destination : ${CYAN}${TRAP_HOST}:${TRAP_PORT}${NC}" echo -e " NetFlow destination: ${CYAN}${NETFLOW_HOST}:${NETFLOW_PORT}${NC}" echo "" read -rp "Proceed with installation? [y/N] " CONFIRM [[ "$CONFIRM" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; } # ───────────────────────────────────────────────────────────────────────────── section "Step 1: System dependencies" # ───────────────────────────────────────────────────────────────────────────── # Detect package manager if command -v apt-get &>/dev/null; then PKG_MGR="apt" elif command -v dnf &>/dev/null; then PKG_MGR="dnf" elif command -v yum &>/dev/null; then PKG_MGR="yum" else error "Cannot detect package manager (apt/dnf/yum). Install dependencies manually." exit 1 fi info "Package manager: $PKG_MGR" install_pkg() { info "Installing: $*" case $PKG_MGR in apt) DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" "$@" ;; dnf) dnf install -y "$@" -q ;; yum) yum install -y "$@" -q ;; esac } # Update package index case $PKG_MGR in apt) DEBIAN_FRONTEND=noninteractive apt-get update -qq DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get upgrade -y -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" ;; dnf|yum) : ;; # no-op, dnf/yum update on install esac # Core dependencies install_pkg python3 python3-pip install_pkg snmp snmp-mibs-downloader 2>/dev/null || install_pkg net-snmp-utils 2>/dev/null || \ warn "Could not install net-snmp-utils — snmpwalk/snmptrap may be unavailable" install_pkg git curl wget # Go (for nflow-generator) if ! command -v go &>/dev/null; then info "Installing Go..." case $PKG_MGR in apt) install_pkg golang-go ;; dnf|yum) install_pkg golang ;; esac else ok "Go already installed: $(go version)" fi ok "System dependencies installed" # ───────────────────────────────────────────────────────────────────────────── section "Step 2: Create snmpsim user and directory structure" # ───────────────────────────────────────────────────────────────────────────── if id "$SNMPSIM_USER" &>/dev/null; then ok "User '$SNMPSIM_USER' already exists" else useradd -r -s /sbin/nologin "$SNMPSIM_USER" ok "Created user: $SNMPSIM_USER" fi # Main directories mkdir -p "${INSTALL_DIR}"/{data,logs,run} for port in $(seq 10000 10009); do mkdir -p "${INSTALL_DIR}/data/port-${port}" done # PID file directory mkdir -p /var/run/snmpsim.commands.responder # Ownership chown -R "${SNMPSIM_USER}:${SNMPSIM_USER}" "${INSTALL_DIR}" chown "${SNMPSIM_USER}:${SNMPSIM_USER}" /var/run/snmpsim.commands.responder ok "Directory structure created: ${INSTALL_DIR}" # ───────────────────────────────────────────────────────────────────────────── section "Step 3: Install snmpsim-lextudio" # ───────────────────────────────────────────────────────────────────────────── if command -v pip3 &>/dev/null; then pip3 install --quiet snmpsim elif command -v pip &>/dev/null; then pip install --quiet snmpsim-lextudio else error "pip not found. Cannot install snmpsim." exit 1 fi # Find the installed binary SNMPSIM_BIN=$(command -v snmpsim-command-responder 2>/dev/null || \ find /usr /root ~/.local -name "snmpsim-command-responder" 2>/dev/null | head -1) if [[ -z "$SNMPSIM_BIN" ]]; then error "snmpsim-command-responder not found after install. Check pip output above." exit 1 fi ok "snmpsim installed: $SNMPSIM_BIN" # ───────────────────────────────────────────────────────────────────────────── section "Step 4: Generate SNMP device data files" # ───────────────────────────────────────────────────────────────────────────── cat > "${INSTALL_DIR}/generate_devices.py" << PYEOF #!/usr/bin/env python3 """ Generate 10 Cisco .snmprec files for snmpsim lab. Each device gets a port-NNNNN/public.snmprec file. Counters use the snmpsim numeric variation module for live incrementing. """ import os, random BASE_OUTPUT_DIR = "${INSTALL_DIR}/data" DEVICES = [ ("core-sw-01", "Cisco IOS Software, Catalyst 6500 Software (s72033_rp-ADVENTERPRISEK9_WAN-M), Version 15.1(2)SY14", "1.3.6.1.4.1.9.1.282", 10000), ("core-sw-02", "Cisco IOS Software, Catalyst 6500 Software (s72033_rp-ADVENTERPRISEK9_WAN-M), Version 15.1(2)SY14", "1.3.6.1.4.1.9.1.282", 10001), ("dist-sw-01", "Cisco IOS Software, Catalyst 4500 Software (cat4500e-UNIVERSALK9-M), Version 15.2(4)E10", "1.3.6.1.4.1.9.1.659", 10002), ("dist-sw-02", "Cisco IOS Software, Catalyst 4500 Software (cat4500e-UNIVERSALK9-M), Version 15.2(4)E10", "1.3.6.1.4.1.9.1.659", 10003), ("access-sw-01", "Cisco IOS Software, Catalyst 3750 Software (C3750E-UNIVERSALK9-M), Version 15.2(4)E9", "1.3.6.1.4.1.9.1.516", 10004), ("access-sw-02", "Cisco IOS Software, Catalyst 3750 Software (C3750E-UNIVERSALK9-M), Version 15.2(4)E9", "1.3.6.1.4.1.9.1.516", 10005), ("access-sw-03", "Cisco IOS Software, Catalyst 3750 Software (C3750E-UNIVERSALK9-M), Version 15.2(4)E9", "1.3.6.1.4.1.9.1.516", 10006), ("rtr-01", "Cisco IOS Software, Version 15.7(3)M8, RELEASE SOFTWARE (fc2) ISR 4321", "1.3.6.1.4.1.9.1.1861", 10007), ("rtr-02", "Cisco IOS Software, Version 15.7(3)M8, RELEASE SOFTWARE (fc2) ISR 4321", "1.3.6.1.4.1.9.1.1861", 10008), ("fw-01", "Cisco Adaptive Security Appliance Software Version 9.16(4)19", "1.3.6.1.4.1.9.1.1902", 10009), ] SWITCH_IFACES = [ ("GigabitEthernet0/1", 1000000000), ("GigabitEthernet0/2", 1000000000), ("GigabitEthernet0/3", 1000000000), ("GigabitEthernet0/4", 1000000000), ("GigabitEthernet0/5", 1000000000), ("TenGigabitEthernet1/1", 10000000000), ("TenGigabitEthernet1/2", 10000000000), ] ROUTER_IFACES = [ ("GigabitEthernet0/0/0", 1000000000), ("GigabitEthernet0/0/1", 1000000000), ("Serial0/1/0", 1544000), ] FW_IFACES = [ ("GigabitEthernet0/0", 1000000000), ("GigabitEthernet0/1", 1000000000), ("GigabitEthernet0/2", 1000000000), ("Management0/0", 1000000000), ] MAX32 = 4294967295 def rand_mac(seed): random.seed(seed) return "".join(f"{random.randint(0,255):02x}" for _ in range(6)) def counter(initial, offset, deviation=0): p = f"initial={initial},offset={offset},cumulative=1,min=0,max={MAX32},wrap=1" if deviation: p += f",deviation={deviation}" return ("65:numeric", p) def gauge(initial, lo, hi, deviation): return ("66:numeric", f"initial={initial},min={lo},max={hi},deviation={deviation},rate=0") def timeticks(initial): return ("67:numeric", f"initial={initial},offset=100,cumulative=1,min=0,max={MAX32}") def ln(oid, tv): return f"{oid}|{tv[0]}|{tv[1]}" def make_snmprec(idx, hostname, descr, sysoid, ifaces): random.seed(idx * 1000) uptime = random.randint(500_000, 80_000_000) cpu_base = random.randint(5, 35) mem_used = random.randint(50_000_000, 200_000_000) mem_free = random.randint(20_000_000, 100_000_000) lines = [] lines.append(f"1.3.6.1.2.1.1.1.0|4|{descr}") lines.append(f"1.3.6.1.2.1.1.2.0|6|{sysoid}") lines.append(ln("1.3.6.1.2.1.1.3.0", timeticks(uptime))) lines.append(f"1.3.6.1.2.1.1.4.0|4|noc@lab.local") lines.append(f"1.3.6.1.2.1.1.5.0|4|{hostname}.lab.local") lines.append(f"1.3.6.1.2.1.1.6.0|4|Lab - Rack A") lines.append(f"1.3.6.1.2.1.1.7.0|2|78") lines.append(f"1.3.6.1.2.1.2.1.0|2|{len(ifaces)}") for i, (ifdescr, ifspeed) in enumerate(ifaces, start=1): random.seed(idx * 200 + i) mac = rand_mac(idx * 100 + i) oper = 1 if i < len(ifaces) else 2 in_oct = random.randint(10_000_000, 999_000_000) out_oct = random.randint(10_000_000, 999_000_000) in_pkt = in_oct // random.randint(64, 512) out_pkt = out_oct // random.randint(64, 512) err = random.randint(0, 50) disc = random.randint(0, 30) if ifspeed >= 10_000_000_000: oo, od = random.randint(5_000_000, 20_000_000), random.randint(5_000, 20_000) elif ifspeed >= 1_000_000_000: oo, od = random.randint(500_000, 5_000_000), random.randint(500, 5_000) else: oo, od = random.randint(10_000, 100_000), random.randint(10, 100) lines += [ f"1.3.6.1.2.1.2.2.1.1.{i}|2|{i}", f"1.3.6.1.2.1.2.2.1.2.{i}|4|{ifdescr}", f"1.3.6.1.2.1.2.2.1.3.{i}|2|6", f"1.3.6.1.2.1.2.2.1.4.{i}|2|1500", f"1.3.6.1.2.1.2.2.1.5.{i}|66|{ifspeed}", f"1.3.6.1.2.1.2.2.1.6.{i}|4x|{mac}", f"1.3.6.1.2.1.2.2.1.7.{i}|2|1", f"1.3.6.1.2.1.2.2.1.8.{i}|2|{oper}", ln(f"1.3.6.1.2.1.2.2.1.10.{i}", counter(in_oct, oo, int(oo*0.1))), ln(f"1.3.6.1.2.1.2.2.1.11.{i}", counter(in_pkt, od, int(od*0.1))), ln(f"1.3.6.1.2.1.2.2.1.13.{i}", counter(disc, 0, 1)), ln(f"1.3.6.1.2.1.2.2.1.14.{i}", counter(err, 0, 1)), ln(f"1.3.6.1.2.1.2.2.1.16.{i}", counter(out_oct, oo, int(oo*0.1))), ln(f"1.3.6.1.2.1.2.2.1.17.{i}", counter(out_pkt, od, int(od*0.1))), ln(f"1.3.6.1.2.1.2.2.1.19.{i}", counter(disc, 0, 1)), ln(f"1.3.6.1.2.1.2.2.1.20.{i}", counter(err, 0, 1)), ] cpu_lo = max(1, cpu_base - 10); cpu_hi = min(99, cpu_base + 20) cpu_dev = max(3, cpu_base // 3) lines += [ f"1.3.6.1.4.1.9.9.109.1.1.1.1.2.1|2|1", ln("1.3.6.1.4.1.9.9.109.1.1.1.1.3.1", gauge(cpu_base, cpu_lo, cpu_hi, cpu_dev)), ln("1.3.6.1.4.1.9.9.109.1.1.1.1.4.1", gauge(cpu_base, cpu_lo, cpu_hi, cpu_dev)), ln("1.3.6.1.4.1.9.9.109.1.1.1.1.5.1", gauge(cpu_base, cpu_lo, cpu_hi, cpu_dev)), ln("1.3.6.1.4.1.9.2.1.57.0", gauge(cpu_base, cpu_lo, cpu_hi, cpu_dev)), ln("1.3.6.1.4.1.9.2.1.58.0", gauge(cpu_base, cpu_lo, cpu_hi, cpu_dev)), f"1.3.6.1.4.1.9.9.48.1.1.1.2.1|4|Processor", ln("1.3.6.1.4.1.9.9.48.1.1.1.5.1", gauge(mem_free, mem_free-5_000_000, mem_free+5_000_000, 2_000_000)), ln("1.3.6.1.4.1.9.9.48.1.1.1.6.1", gauge(mem_used, mem_used-5_000_000, mem_used+5_000_000, 2_000_000)), f"1.3.6.1.4.1.9.9.48.1.1.1.2.2|4|I/O", ln("1.3.6.1.4.1.9.9.48.1.1.1.5.2", gauge(mem_free//4, mem_free//4-1_000_000, mem_free//4+1_000_000, 500_000)), ln("1.3.6.1.4.1.9.9.48.1.1.1.6.2", gauge(mem_used//8, mem_used//8-500_000, mem_used//8+500_000, 250_000)), ] return "\n".join(lines) + "\n" for idx, (hostname, descr, sysoid, port) in enumerate(DEVICES): ifaces = ROUTER_IFACES if "rtr" in hostname else (FW_IFACES if "fw" in hostname else SWITCH_IFACES) content = make_snmprec(idx, hostname, descr, sysoid, ifaces) out_dir = os.path.join(BASE_OUTPUT_DIR, f"port-{port}") os.makedirs(out_dir, exist_ok=True) with open(os.path.join(out_dir, "public.snmprec"), "w") as f: f.write(content) print(f" port-{port}/public.snmprec ({hostname}, {len(ifaces)} interfaces)") print("Done.") PYEOF info "Generating device data files..." python3 "${INSTALL_DIR}/generate_devices.py" chown -R "${SNMPSIM_USER}:${SNMPSIM_USER}" "${INSTALL_DIR}/data" ok "Device data files generated" # ───────────────────────────────────────────────────────────────────────────── section "Step 5: Create start / stop scripts" # ───────────────────────────────────────────────────────────────────────────── cat > "${INSTALL_DIR}/start-sim.sh" << EOF #!/bin/bash # ============================================================================= # Start 10 Cisco SNMP agents — all SNMPv2c, community: public # ============================================================================= SNMPSIM_BIN="${SNMPSIM_BIN}" BASE_DATA_DIR="${INSTALL_DIR}/data" LOGFILE="${INSTALL_DIR}/logs/snmpsim.log" PID_DIR="/var/run/snmpsim.commands.responder" VM_IP="${VM_IP}" declare -A DEVICES=( [10000]="core-sw-01" [10001]="core-sw-02" [10002]="dist-sw-01" [10003]="dist-sw-02" [10004]="access-sw-01" [10005]="access-sw-02" [10006]="access-sw-03" [10007]="rtr-01" [10008]="rtr-02" [10009]="fw-01" ) echo "Starting 10 Cisco SNMP agents on \${VM_IP} (SNMPv2c, community: public)..." > "\${LOGFILE}" for PORT in \$(echo "\${!DEVICES[@]}" | tr ' ' '\n' | sort -n); do NAME="\${DEVICES[\$PORT]}" \${SNMPSIM_BIN} \\ --agent-udpv4-endpoint=\${VM_IP}:\${PORT} \\ --data-dir="\${BASE_DATA_DIR}/port-\${PORT}" \\ --process-user=${SNMPSIM_USER} \\ --process-group=${SNMPSIM_USER} \\ --pid-file="\${PID_DIR}/\${PORT}.pid" \\ --daemonize \\ --logging-method=file:\${LOGFILE} echo " Started: \${NAME} on \${VM_IP}:\${PORT}" done echo "" echo "Done. 10 agents running (SNMPv2c, community: public)" echo "Verify: snmpwalk -v2c -c public \${VM_IP}:10000 1.3.6.1.2.1.1" EOF cat > "${INSTALL_DIR}/stop-sim.sh" << 'EOF' #!/bin/bash PID_DIR="/var/run/snmpsim.commands.responder" echo "Stopping snmpsim agents..." STOPPED=0 for pidfile in "${PID_DIR}"/*.pid; do [[ -f "$pidfile" ]] || continue PID=$(cat "$pidfile" 2>/dev/null) PORT=$(basename "$pidfile" .pid) if kill "$PID" 2>/dev/null; then echo " Stopped port $PORT (PID $PID)" STOPPED=$((STOPPED+1)) fi rm -f "$pidfile" done if [[ $STOPPED -eq 0 ]]; then pkill -f snmpsim-command-responder 2>/dev/null && echo " Stopped via pkill" || echo " No snmpsim process found." fi echo "Done." EOF cat > "${INSTALL_DIR}/restart-sim.sh" << 'EOF' #!/bin/bash cd "$(dirname "$0")" ./stop-sim.sh sleep 2 rm -rf /tmp/snmpsim/* ./start-sim.sh EOF chmod +x "${INSTALL_DIR}"/{start-sim.sh,stop-sim.sh,restart-sim.sh} ok "start-sim.sh, stop-sim.sh, restart-sim.sh created" # ───────────────────────────────────────────────────────────────────────────── section "Step 6: Create trap generator" # ───────────────────────────────────────────────────────────────────────────── cat > "${INSTALL_DIR}/trap-generator.sh" << EOF #!/bin/bash # ============================================================================= # trap-generator.sh # Sends one random SNMP trap to the configured destination. # Run manually or via cron. # # Usage: ./trap-generator.sh [--host ] [--port ] # Defaults to compiled-in values if no args given. # ============================================================================= SEVONE_IP="\${1:-${TRAP_HOST}}" SEVONE_PORT="\${2:-${TRAP_PORT}}" COMMUNITY="public" declare -A DEVICES DEVICES["core-sw-01"]="${VM_IP}:10000:1.3.6.1.4.1.9.1.282" DEVICES["core-sw-02"]="${VM_IP}:10001:1.3.6.1.4.1.9.1.282" DEVICES["dist-sw-01"]="${VM_IP}:10002:1.3.6.1.4.1.9.1.659" DEVICES["dist-sw-02"]="${VM_IP}:10003:1.3.6.1.4.1.9.1.659" DEVICES["access-sw-01"]="${VM_IP}:10004:1.3.6.1.4.1.9.1.516" DEVICES["access-sw-02"]="${VM_IP}:10005:1.3.6.1.4.1.9.1.516" DEVICES["access-sw-03"]="${VM_IP}:10006:1.3.6.1.4.1.9.1.516" DEVICES["rtr-01"]="${VM_IP}:10007:1.3.6.1.4.1.9.1.1861" DEVICES["rtr-02"]="${VM_IP}:10008:1.3.6.1.4.1.9.1.1861" DEVICES["fw-01"]="${VM_IP}:10009:1.3.6.1.4.1.9.1.1902" SWITCH_IFACES=("GigabitEthernet0/1" "GigabitEthernet0/2" "GigabitEthernet0/3" "GigabitEthernet0/4" "GigabitEthernet0/5" "TenGigabitEthernet1/1" "TenGigabitEthernet1/2") ROUTER_IFACES=("GigabitEthernet0/0/0" "GigabitEthernet0/0/1" "Serial0/1/0") FW_IFACES=("GigabitEthernet0/0" "GigabitEthernet0/1" "GigabitEthernet0/2" "Management0/0") random_element() { local a=("\$@"); echo "\${a[\$RANDOM % \${#a[@]}]}"; } random_device() { local k=("\${!DEVICES[@]}"); echo "\${k[\$RANDOM % \${#k[@]}]}"; } get_ifaces() { case "\$1" in rtr*) echo "\${ROUTER_IFACES[@]}" ;; fw*) echo "\${FW_IFACES[@]}" ;; *) echo "\${SWITCH_IFACES[@]}" ;; esac } send_linkDown() { local dev="\$1"; local ifaces=(\$(get_ifaces "\$dev")) local iface=\$(random_element "\${ifaces[@]}"); local idx=\$((RANDOM % \${#ifaces[@]} + 1)) echo "[\$(date '+%H:%M:%S')] linkDown | \$dev | \$iface" snmptrap -v2c -c "\$COMMUNITY" "\$SEVONE_IP:\$SEVONE_PORT" "" 1.3.6.1.6.3.1.1.5.3 \\ 1.3.6.1.2.1.2.2.1.1.\$idx i \$idx 1.3.6.1.2.1.2.2.1.2.\$idx s "\$iface" \\ 1.3.6.1.2.1.2.2.1.7.\$idx i 2 1.3.6.1.2.1.2.2.1.8.\$idx i 2 } send_linkUp() { local dev="\$1"; local ifaces=(\$(get_ifaces "\$dev")) local iface=\$(random_element "\${ifaces[@]}"); local idx=\$((RANDOM % \${#ifaces[@]} + 1)) echo "[\$(date '+%H:%M:%S')] linkUp | \$dev | \$iface" snmptrap -v2c -c "\$COMMUNITY" "\$SEVONE_IP:\$SEVONE_PORT" "" 1.3.6.1.6.3.1.1.5.4 \\ 1.3.6.1.2.1.2.2.1.1.\$idx i \$idx 1.3.6.1.2.1.2.2.1.2.\$idx s "\$iface" \\ 1.3.6.1.2.1.2.2.1.7.\$idx i 1 1.3.6.1.2.1.2.2.1.8.\$idx i 1 } send_coldStart() { echo "[\$(date '+%H:%M:%S')] coldStart | \$1" snmptrap -v2c -c "\$COMMUNITY" "\$SEVONE_IP:\$SEVONE_PORT" "" 1.3.6.1.6.3.1.1.5.1 } send_warmStart() { echo "[\$(date '+%H:%M:%S')] warmStart | \$1" snmptrap -v2c -c "\$COMMUNITY" "\$SEVONE_IP:\$SEVONE_PORT" "" 1.3.6.1.6.3.1.1.5.2 } send_ospfNbrStateChange() { [[ "\$1" != rtr* ]] && return local peer="10.\$((RANDOM%255)).\$((RANDOM%255)).\$((RANDOM%255))" local state=\$((RANDOM%8+1)) echo "[\$(date '+%H:%M:%S')] ospfNbrStateChange| \$1 | peer \$peer state \$state" snmptrap -v2c -c "\$COMMUNITY" "\$SEVONE_IP:\$SEVONE_PORT" "" 1.3.6.1.4.1.9.10.99.0.1 \\ 1.3.6.1.2.1.14.10.1.1.\$peer a "\$peer" 1.3.6.1.2.1.14.10.1.6.\$peer i \$state \\ 1.3.6.1.2.1.14.4.1.2.0 s "0.0.0.0" } send_bgpEstablished() { [[ "\$1" != rtr* ]] && return local peer="192.168.\$((RANDOM%255)).\$((RANDOM%255))" local as=\$((RANDOM%65000+1000)) echo "[\$(date '+%H:%M:%S')] bgpEstablished | \$1 | peer \$peer AS\$as" snmptrap -v2c -c "\$COMMUNITY" "\$SEVONE_IP:\$SEVONE_PORT" "" 1.3.6.1.2.1.15.7.1 \\ 1.3.6.1.2.1.15.3.1.7.\$peer i 6 1.3.6.1.2.1.15.3.1.9.\$peer i \$as } send_bgpBackwardTransition() { [[ "\$1" != rtr* ]] && return local peer="192.168.\$((RANDOM%255)).\$((RANDOM%255))" local as=\$((RANDOM%65000+1000)); local state=\$((RANDOM%5+1)) echo "[\$(date '+%H:%M:%S')] bgpBackward | \$1 | peer \$peer state \$state" snmptrap -v2c -c "\$COMMUNITY" "\$SEVONE_IP:\$SEVONE_PORT" "" 1.3.6.1.2.1.15.7.2 \\ 1.3.6.1.2.1.15.3.1.7.\$peer i \$state 1.3.6.1.2.1.15.3.1.9.\$peer i \$as } send_cpuThreshold() { local cpu=\$((RANDOM%40+60)) echo "[\$(date '+%H:%M:%S')] cpuThreshold | \$1 | CPU \${cpu}%" snmptrap -v2c -c "\$COMMUNITY" "\$SEVONE_IP:\$SEVONE_PORT" "" 1.3.6.1.4.1.9.9.109.0.1 \\ 1.3.6.1.4.1.9.9.109.1.1.1.1.3.1 i \$cpu \\ 1.3.6.1.4.1.9.9.109.1.1.1.1.4.1 i \$((cpu-RANDOM%10)) \\ 1.3.6.1.4.1.9.9.109.1.1.1.1.5.1 i \$((cpu-RANDOM%15)) } TRAP_TYPES=(linkDown linkDown linkDown linkUp linkUp linkUp coldStart warmStart ospfNbrStateChange ospfNbrStateChange bgpEstablished bgpBackwardTransition cpuThreshold) trap_type=\$(random_element "\${TRAP_TYPES[@]}") device=\$(random_device) echo "--- \$(date '+%Y-%m-%d %H:%M:%S') | \$trap_type from \$device -> \$SEVONE_IP:\$SEVONE_PORT ---" case "\$trap_type" in linkDown) send_linkDown "\$device" ;; linkUp) send_linkUp "\$device" ;; coldStart) send_coldStart "\$device" ;; warmStart) send_warmStart "\$device" ;; ospfNbrStateChange) send_ospfNbrStateChange "\$device" ;; bgpEstablished) send_bgpEstablished "\$device" ;; bgpBackwardTransition) send_bgpBackwardTransition "\$device" ;; cpuThreshold) send_cpuThreshold "\$device" ;; esac EOF chmod +x "${INSTALL_DIR}/trap-generator.sh" ok "trap-generator.sh created" # ───────────────────────────────────────────────────────────────────────────── section "Step 7: Build and install nflow-generator" # ───────────────────────────────────────────────────────────────────────────── NFLOW_BIN="${INSTALL_DIR}/nflow-generator" if [[ -f "$NFLOW_BIN" ]]; then ok "nflow-generator already present — skipping build" else info "Cloning and building nflow-generator..." TMPDIR=$(mktemp -d) git clone --quiet https://github.com/nerdalert/nflow-generator.git "$TMPDIR/nflow-generator" cd "$TMPDIR/nflow-generator" go build -o "$NFLOW_BIN" . cd / rm -rf "$TMPDIR" ok "nflow-generator built: $NFLOW_BIN" fi # ───────────────────────────────────────────────────────────────────────────── section "Step 8: Create systemd services" # ───────────────────────────────────────────────────────────────────────────── # NetFlow systemd service cat > /etc/systemd/system/snmpsim-netflow.service << EOF [Unit] Description=SevOne Lab NetFlow Generator After=network.target [Service] ExecStart=${INSTALL_DIR}/nflow-generator -t ${NETFLOW_HOST} -p ${NETFLOW_PORT} Restart=always RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target EOF # Trap generator systemd timer (every 5 minutes) cat > /etc/systemd/system/snmpsim-traps.service << EOF [Unit] Description=SevOne Lab Trap Generator (single run) [Service] Type=oneshot ExecStart=${INSTALL_DIR}/trap-generator.sh ${TRAP_HOST} ${TRAP_PORT} StandardOutput=journal StandardError=journal EOF cat > /etc/systemd/system/snmpsim-traps.timer << EOF [Unit] Description=SevOne Lab Trap Generator — every 5 minutes [Timer] OnBootSec=60 OnUnitActiveSec=5min Unit=snmpsim-traps.service [Install] WantedBy=timers.target EOF # SNMP simulator systemd service # Type=oneshot + RemainAfterExit=yes is correct for a script that launches # background daemons and exits. HOME must be set so snmpsim can find its # variation modules path (~/.snmpsim/variation). cat > /etc/systemd/system/snmpsim-agents.service << EOF [Unit] Description=SevOne Lab SNMP Simulator (10 Cisco agents) After=network.target [Service] Type=oneshot RemainAfterExit=yes Environment=HOME=/root ExecStart=${INSTALL_DIR}/start-sim.sh ExecStop=${INSTALL_DIR}/stop-sim.sh ExecReload=${INSTALL_DIR}/restart-sim.sh [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable --now snmpsim-agents.service systemctl enable --now snmpsim-netflow.service systemctl enable --now snmpsim-traps.timer ok "systemd services created, enabled, and started:" ok " snmpsim-agents.service (SNMP -> ${VM_IP}:10000-10009, community: public)" ok " snmpsim-netflow.service (NetFlow -> ${NETFLOW_HOST}:${NETFLOW_PORT})" ok " snmpsim-traps.timer (Traps -> ${TRAP_HOST}:${TRAP_PORT} every 5 min)" # ───────────────────────────────────────────────────────────────────────────── section "Step 9: Fix ownership" # ───────────────────────────────────────────────────────────────────────────── chown -R "${SNMPSIM_USER}:${SNMPSIM_USER}" "${INSTALL_DIR}" chown root:root "${INSTALL_DIR}/start-sim.sh" "${INSTALL_DIR}/stop-sim.sh" "${INSTALL_DIR}/restart-sim.sh" chmod +x "${INSTALL_DIR}/start-sim.sh" "${INSTALL_DIR}/stop-sim.sh" "${INSTALL_DIR}/restart-sim.sh" # ───────────────────────────────────────────────────────────────────────────── section "Step 10: Verify installation" # ───────────────────────────────────────────────────────────────────────────── sleep 3 PASS=0; FAIL=0 check() { local desc="$1"; local cmd="$2" if eval "$cmd" &>/dev/null; then ok "$desc" PASS=$((PASS+1)) else error "$desc" FAIL=$((FAIL+1)) fi } check "snmpsim-agents service active" "systemctl is-active --quiet snmpsim-agents" check "Port 10000 bound (core-sw-01)" "ss -ulnp | grep -q ':10000'" check "Port 10007 bound (rtr-01)" "ss -ulnp | grep -q ':10007'" check "SNMPv2c response (port 10000)" "snmpget -v2c -c public ${VM_IP}:10000 1.3.6.1.2.1.1.5.0 2>/dev/null" check "SNMPv2c response (port 10005)" "snmpget -v2c -c public ${VM_IP}:10005 1.3.6.1.2.1.1.5.0 2>/dev/null" check "SNMPv2c response (port 10009)" "snmpget -v2c -c public ${VM_IP}:10009 1.3.6.1.2.1.1.5.0 2>/dev/null" check "nflow-generator service active" "systemctl is-active --quiet snmpsim-netflow" check "trap timer active" "systemctl is-active --quiet snmpsim-traps.timer" echo "" echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}" echo -e " Checks passed: ${GREEN}${PASS}${NC} Checks failed: ${RED}${FAIL}${NC}" echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}" # ───────────────────────────────────────────────────────────────────────────── # Final summary # ───────────────────────────────────────────────────────────────────────────── echo "" echo -e "${BLUE}╔══════════════════════════════════════════════════════════╗${NC}" echo -e "${BLUE}║ Installation complete ║${NC}" echo -e "${BLUE}╚══════════════════════════════════════════════════════════╝${NC}" echo "" echo -e " Install directory : ${CYAN}${INSTALL_DIR}${NC}" echo -e " VM IP : ${CYAN}${VM_IP}${NC}" echo -e " Trap destination : ${CYAN}${TRAP_HOST}:${TRAP_PORT}${NC}" echo -e " NetFlow dest : ${CYAN}${NETFLOW_HOST}:${NETFLOW_PORT}${NC}" echo "" echo -e " SNMP devices : ${CYAN}${VM_IP}:10000 - ${VM_IP}:10009${NC}" echo -e " SNMP version : ${CYAN}v2c${NC}" echo -e " Community string : ${CYAN}public${NC}" echo "" echo -e " ${YELLOW}Add each device to SevOne:${NC}" echo -e " IP: ${VM_IP}, SNMP Version: v2c, Community: public, Port: 10000 through 10009" echo "" echo -e " ${YELLOW}Useful commands:${NC}" echo -e " ${INSTALL_DIR}/start-sim.sh" echo -e " ${INSTALL_DIR}/stop-sim.sh" echo -e " ${INSTALL_DIR}/restart-sim.sh" echo -e " ${INSTALL_DIR}/trap-generator.sh # send one trap now" echo -e " systemctl status snmpsim-netflow" echo -e " systemctl status snmpsim-traps.timer" echo -e " tail -f ${INSTALL_DIR}/logs/snmpsim.log" echo ""