Architecture
This document explains SimNet's architecture in detail, covering the tech stack, component hierarchy, data flow, and theming system.
1. Tech Stack Overview
| Layer | Technology | Role |
|---|---|---|
| Static Site Generator | VitePress 2.0 (SSG) | Renders Markdown content into static pages with custom layout support |
| UI Framework | Vue 3 Composition API | Reactive UI component system |
| Styling | Tailwind CSS 4.2 | Utility-first CSS framework, paired with CSS custom properties for theme switching |
| Simulation Engine | Rust / WebAssembly (WASM) | Network protocol simulation core, compiled to WASM and run in the browser |
| Terminal | xterm.js 6.0 | In-browser terminal emulator that provides the CLI interaction surface |
| Topology View | Canvas 2D | Visual rendering of the network topology |
2. Architecture Diagram
┌─────────────────────────────────────────────┐
│ VitePress (SSG) │
│ ┌────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ Layout │→ │ SimNet │→ │ Resizable │ │
│ │ .vue │ │ .vue │ │ Panel .vue │ │
│ └────────┘ └────┬─────┘ └──────┬──────┘ │
│ │ │ │
│ ┌──────────────┼───────────────┤ │
│ ▼ ▼ ▼ │
│ Topology TrafficLog Terminal │
│ (Canvas2D) (Vue DOM) (xterm.js) │
│ │ │ │ │
│ └──────────────┼───────────────┘ │
│ ▼ │
│ useSimulation.ts │
│ useTerminal.ts │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ WASM Bridge │ │
│ │ (simnet_wasm) │ │
│ └────────┬────────┘ │
└──────────────────┼───────────────────────────┘
▼
┌─────────────────┐
│ Rust Engine │
│ (simnet-core) │
│ Protocols, Sim │
└─────────────────┘Layer Notes
Top layer: VitePress + Vue components
Layout.vueis the global entry point; it inspects the frontmatterlayout: challengeflag to decide whether to enable challenge modeChallengeLayout.vuewires upSimNet.vueand injects the Markdown content into the#missionslotSimNet.vueis the core container — it initializes the simulation engine and usesprovide/injectto pass the engine instance and packet data down to child components
Middle layer: composable logic
useSimulation.ts— manages the WASM engine lifecycle, therequestAnimationFrameloop, and packet normalizationuseChallenge.ts— loads challenge configuration, manages the state machine (loading → running → completed), and handles Flag verificationuseTerminal.ts— bridges xterm.js to the WASM engine's command interface for CLI interaction
Bottom layer: Rust WASM engine
- Initialized from a JSON config; simulates network topology, protocol behavior, and the event system
- Exposes the
SimNetEngineclass to JavaScript viawasm-bindgen
3. Directory Structure
network-sim/
├── .vitepress/
│ ├── theme/
│ │ ├── layouts/
│ │ │ ├── Layout.vue # Global layout — detects challenge pages
│ │ │ └── ChallengeLayout.vue # Challenge layout — full-screen simulator
│ │ ├── components/
│ │ │ ├── SimNet.vue # Core container — initializes engine, provides data
│ │ │ ├── ResizablePanel.vue # Draggable resizable panel system
│ │ │ ├── TopologyView.vue # Canvas 2D network topology view
│ │ │ ├── TrafficLog.vue # Packet traffic log (Vue DOM rendering)
│ │ │ ├── Terminal.vue # xterm.js terminal component
│ │ │ ├── IconBar.vue # Side icon bar
│ │ │ ├── IconBarPanel.vue # Expanded panel for the icon bar
│ │ │ ├── FlagSubmit.vue # Flag submission form (embedded in nav bar)
│ │ │ ├── DeviceModal.vue # Device details modal
│ │ │ └── ChallengeSidebar.vue # Challenge list sidebar
│ │ ├── composables/
│ │ │ ├── useSimulation.ts # WASM engine management + RAF loop
│ │ │ ├── useChallenge.ts # Challenge state machine + Flag verification
│ │ │ ├── useTerminal.ts # Terminal command bridge
│ │ │ ├── useBreakpoint.ts # Responsive breakpoint detection
│ │ │ └── injectionKeys.ts # Vue provide/inject Symbol keys
│ │ ├── loaders/
│ │ │ └── challenge.data.ts # VitePress data loader — loads challenge data at build time
│ │ ├── styles/
│ │ │ ├── animations.css # Animation definitions
│ │ │ └── vitepress-overrides.css # Overrides for VitePress defaults
│ │ ├── style.css # SimNet color system (CSS custom properties)
│ │ └── index.ts # Theme entry point
│ └── types/
│ └── wasm.d.ts # WASM module type declarations
├── docs/
│ ├── index.md # Home page
│ ├── challenges/
│ │ ├── index.md # Challenge list page
│ │ ├── flags.secret.yaml # Flag plaintext (not committed)
│ │ └── 01-ethernet-basics/ # Example scenario
│ │ ├── index.md # Markdown page (layout set in frontmatter)
│ │ └── config.yaml # Topology, events, Flag settings
│ └── public/
│ └── wasm/ # Compiled WASM artifacts
│ ├── simnet_wasm.js # JS glue code
│ ├── simnet_wasm_bg.wasm # WASM binary
│ └── simnet_wasm.d.ts # TypeScript type definitions
├── simnet-engine/ # Rust workspace
│ └── crates/
│ ├── simnet-core/ # Core types: Device, Frame, Protocol, Event
│ ├── simnet-devices/ # Device implementations: PC, Switch, Server
│ ├── simnet-protocols/ # Protocol implementations: Ethernet, ARP, IPv4, TCP, HTTP
│ ├── simnet-sim/ # Simulation engine: Topology, Simulation, EventEngine
│ ├── simnet-crypto/ # Crypto module: Flag encryption and verification
│ └── simnet-wasm/ # WASM bridge layer: wasm-bindgen API
└── tests/ # Vitest tests4. Data Flow
The following describes how the simulation engine drives the whole system on each tick (clock cycle).
Full flow of a single tick
requestAnimationFrame loop
│
▼
engine.tick() ← Call into WASM: advance the simulation by one tick
│
▼
engine.get_new_packets() ← Retrieve new packets produced this tick (JSON array)
│
▼
normalizePacket() ← Convert snake_case fields to camelCase
(srcDevice, dstDevice, protocol, summary, layers...)
│
▼
packets.value = [...] ← Update Vue reactive ref (capped at 500 entries)
│
▼
provide(SIM_PACKETS_KEY) ← Pass to child components via Vue provide/inject
│
┌────┼────┐
▼ ▼ ▼
Topology TrafficLog Terminal
(Canvas) (Vue DOM) (xterm.js)Initialization flow
- The user navigates to a challenge page and
ChallengeLayout.vueis mounted SimNet.vue'sonMountedtriggersloadConfig(path)useChallenge.tsfetchesconfig.yamlviafetch()(already converted to JSON by VitePress at build time)- The config JSON is passed into
useSimulation.init(configJson) init()dynamically loads the WASM module (import('/wasm/simnet_wasm.js')) and calls__wbg_init()to initialize it- A
SimNetEngineinstance is constructed from the config JSON - The
requestAnimationFrameloop starts and the simulation begins running
Packet normalization
Packets returned by the WASM engine use Rust-style snake_case field names. The normalizePacket() function converts them to the camelCase form expected by the front end:
| WASM Field | After Normalization |
|---|---|
src_device | srcDevice |
dst_device | dstDevice |
protocol | protocol (unchanged) |
summary | summary (unchanged) |
layers | layers (unchanged) |
Each packet is assigned an incrementing unique id for tracking via Vue's v-for :key.
5. Theming System
SimNet implements dark/light theme switching with CSS custom properties, integrated with VitePress's .dark class mechanism.
Color architecture
All colors are defined in .vitepress/theme/style.css and grouped into these categories:
| Category | Variable Prefix | Examples |
|---|---|---|
| Base | --color-sim-bg, --color-sim-panel | Background, panel, border |
| Accent | --color-sim-accent | Dark mode: Cyber Green #00ffaa; light mode: Indigo #4f46e5 |
| Semantic | --color-sim-info, --color-sim-warn, --color-sim-danger | State indicators |
| Text | --color-sim-text, --color-sim-text-muted, --color-sim-text-dim | Primary, secondary, dim text |
| Protocol | --color-proto-ethernet, --color-proto-arp, ... | Per-protocol packet coloring |
Dark / light switching
/* Dark mode (default) */
@theme {
--color-sim-bg: #101828;
--color-sim-accent: #00ffaa;
/* ... */
}
/* Light-mode overrides */
:root:not(.dark) {
--color-sim-bg: #f1f5f9;
--color-sim-accent: #4f46e5;
/* ... */
}VitePress toggles the .dark class on the <html> element, and every SimNet component follows automatically.
Terminal stays dark
Even in light mode, the terminal panel stays dark (matching the visual convention of real-world terminals):
:root:not(.dark) .sim-terminal {
--color-sim-panel: #1e293b;
--color-sim-bg: #0f172a;
/* ... */
}6. Challenge Framework
The challenge system's data flow starts from a YAML config and goes through several stages before being rendered as an interactive simulation.
Challenge loading flow
config.yaml frontmatter (index.md)
│ │
▼ ▼
VitePress build Layout.vue
(YAML → JSON) detects layout: "challenge"
│ │
▼ ▼
fetch(config.json) ChallengeLayout.vue
│ │
▼ ▼
useChallenge.ts SimNet.vue
(state machine) (provide engine + packets)
│ │
▼ ▼
useSimulation.init() Child components render
(WASM engine init) Topology / TrafficLog / TerminalChallenge state machine
useChallenge.ts manages four states:
loading ──→ running ──→ completed
│ ▲
└──→ error │
submitFlag(correct)- loading: loading the config and initializing the WASM engine
- running: the simulation is running and the user can observe packets and issue commands
- completed: Flag verification succeeded
- error: config failed to load or WASM failed to initialize
Flag verification
Flag verification runs in the browser via the Web Crypto API: a SHA-256 hash of the user input is compared against the hash stored in the config. The plaintext flag never appears in front-end code.
user input → SHA-256(input) → compare with config.flag.hash → correct / incorrectResponsive layout
SimNet.vue uses useBreakpoint.ts to detect viewport width and offers three layouts:
| Breakpoint | Layout | Highlights |
|---|---|---|
| Desktop | Three-panel + side icon bar | ResizablePanel can be dragged to resize |
| Tablet | Top tab switcher + bottom terminal | Topology / Traffic tab switching |
| Mobile | Bottom nav bar + single full-screen panel | Five tabs: Topo / Traffic / Term / Mission / Hints |
7. Semantic Engine
The Semantic Engine parses the challenge config (config.yaml) and builds a semantic model. It does more than read topology data — it understands the network's semantics: routing relationships, DNS resolution chains, and service reachability.
Responsibilities
- Topology modeling: converts
topology.nodesandtopology.linksinto a graph structure and computes reachability - Routing semantics: builds routing tables and forwarding logic from each device's IP/subnet settings
- DNS semantics: parses DNS service config and builds the domain → IP mapping table
- Service semantics: marks which devices provide which services (HTTP, DNS) so the Behavior Engine can dispatch events accordingly
Fault and attack model
The DSL v2 faults and attacks sections are parsed by the Semantic Engine into semantic operations:
- Faults: missing routes, incorrect DNS records, disabled interfaces — these are "damaged" initial states
- Attacks: ARP spoofing, MITM, DoS — these are malicious behaviors continuously happening in the environment
8. Behavior Engine
The Behavior Engine drives the simulation's dynamic behavior, including event rule execution, fault/attack expansion, and win-condition evaluation.
Event rules
Event rules are defined in the events block of config.yaml. The Behavior Engine evaluates them on every tick:
- Time-triggered:
periodic_frame,periodic_communication, etc. fire on theirinterval_mscadence - Condition-triggered: fires when the condition in the
triggerfield becomes true (e.g.mitm_active) - Chain-triggered: one event's outcome can trigger another
Fault / attack expansion
The fault and attack model produced by the Semantic Engine is expanded by the Behavior Engine into concrete event sequences when the simulation starts:
- Missing route → delete a specific entry from the routing table
- ARP spoofing → periodically inject forged ARP replies
- DoS flood → produce a large volume of SYN packets
Win condition evaluation
win_condition defines how a challenge is judged complete. The Behavior Engine checks it after every tick:
| Type | Description |
|---|---|
capture_payload | The player captured a packet containing specific content |
connectivity_restored | Connectivity between specified devices is restored |
defense_active | The player successfully enabled all required defenses |
service_available | A specified service is back in working order |
9. DSL v2 Architecture
DSL v2 is SimNet's challenge configuration language, carried in config.yaml, that describes the full challenge scenario.
Data flow
graph TB
YAML[config.yaml] --> Parser[DSL v2 Parser]
Parser --> SM[Semantic Model]
SM --> BE[Behavior Engine]
BE --> Sim[Simulation Loop]
Sim --> UI[Vue Components]
UI --> TL[TrafficLog]
UI --> TV[TopologyView]
UI --> Term[Terminal]config.yaml structure overview
challenge: # Challenge metadata
id, title, difficulty, category, flag
topology: # Network topology
nodes: [...] # Device definitions
links: [...] # Link definitions
faults: # Fault definitions (used by Network Repair scenarios)
- type: missing_route
device: router1
destination: 10.0.2.0/24
attacks: # Attack definitions (used by Security Attack / Transmission Defense scenarios)
- type: arp_spoofing
attacker: eve
target: pc1
impersonates: gateway
player_tools: # Tools available to the player
- tcpdump # Packet capture
- arp # ARP table inspection
- ip # Routing and interface management
- ping # Connectivity test
hints: # Staged hints
- trigger: after_30s
text: "..."
win_condition: # Win condition
type: capture_payload | connectivity_restored | defense_activeFrom config.yaml to simulator
- VitePress build: YAML is converted to JSON and flag placeholders are substituted
- DSL v2 Parser: validates structure, expands syntactic sugar, resolves references
- Semantic Model: builds the network's semantic model (routing table, DNS table, service table)
- Behavior Engine: expands faults / attacks / events into an event queue
- Simulation Loop: advances the simulation each tick and evaluates the win condition
- Vue Components: reactively render the topology view, packet log, and terminal
Next, read Extending the Template to learn how to author new content for SimNet.