From 5b9aa33d4fc43a1354410976c44a4e08b2cc6252 Mon Sep 17 00:00:00 2001 From: Aleksei Fedorov Date: Fri, 13 Mar 2026 11:11:25 +0000 Subject: [PATCH] crypto page --- app.py | 249 +++++++++++++++ index.html | 6 +- static/js/crypto-belt.js | 470 ++++++++++++++++++++++++++++- static/js/main.js | 9 + static/js/right-semicircle-menu.js | 17 +- 5 files changed, 742 insertions(+), 9 deletions(-) diff --git a/app.py b/app.py index 9621125..d17be29 100644 --- a/app.py +++ b/app.py @@ -708,6 +708,11 @@ def index(): # Serve index.html from root directory return send_from_directory('.', 'index.html') +@app.route('/crypto') +def crypto_page(): + """SEO-friendly crypto subsystem page.""" + return render_template('crypto.html') + @app.route('/api/syscalls-realtime') def syscalls_realtime(): """API for real-time system calls""" @@ -3337,6 +3342,231 @@ def collect_algorithm_competition(requested_algorithm="aes"): "selection_policy": "max-priority" } +def collect_kernel_crypto_clients(items): + """Infer major kernel crypto clients from active process/protocol context.""" + client_rules = [ + ("kTLS", ["nginx", "haproxy", "envoy", "caddy", "apache", "httpd", "traefik"], "TLS"), + ("WireGuard", ["wg", "wireguard"], "WireGuard"), + ("IPsec/XFRM", ["charon", "strongswan", "ipsec", "racoon"], "TLS"), + ("dm-crypt", ["cryptsetup", "dmcrypt", "luks"], "CRYPTO API"), + ("fscrypt", ["fscrypt"], "CRYPTO API"), + ("AF_ALG", ["openssl", "python", "curl", "wget"], "CRYPTO API") + ] + results = [] + lowered_items = [] + for item in items: + lowered_items.append({ + "process": str(item.get("process", "")).lower(), + "protocol": str(item.get("protocol", "")), + "source_kind": str(item.get("source_kind", "")) + }) + + for name, tokens, proto_hint in client_rules: + flows = 0 + for item in lowered_items: + proc = item["process"] + proto = item["protocol"] + if any(token in proc for token in tokens): + flows += 1 + elif proto_hint and proto == proto_hint: + flows += 1 + status = "active" if flows > 0 else "idle" + results.append({ + "name": name, + "status": status, + "active_flows": int(flows) + }) + return results + +def collect_sync_async_queue(items): + """Estimate sync/async crypto execution pressure from active flows.""" + active_items = [i for i in items if str(i.get("status", "")).upper() != "LISTEN"] + async_items = [ + i for i in active_items + if str(i.get("source_kind", "")) == "connection" + or str(i.get("protocol", "")).upper() in {"TLS", "WIREGUARD", "SSH"} + ] + sync_items = max(len(active_items) - len(async_items), 0) + sum( + 1 for i in items if str(i.get("source_kind", "")) == "process" + ) + queue_depth = max(len(async_items) - 1, 0) + queue_latency_ms = round(0.35 + min(5.5, queue_depth * 0.42 + len(active_items) * 0.08), 2) + return { + "sync_ops_est": int(sync_items), + "async_ops_est": int(len(async_items)), + "queue_depth_est": int(queue_depth), + "queue_latency_ms_est": queue_latency_ms, + "mode": "heuristic" + } + +def collect_hw_offload_status(entries, algorithm_competitions): + """Estimate hardware acceleration availability from /proc/crypto drivers.""" + names = [] + for entry in entries: + n = str(entry.get("name", "")).lower() + d = str(entry.get("driver", "")).lower() + if n: + names.append(n) + if d: + names.append(d) + + def has_token(tokens): + return any(any(token in item for token in tokens) for item in names) + + selected_impls = { + key: str(value.get("selected", {}).get("name", "")).lower() + for key, value in (algorithm_competitions or {}).items() + } + selected_joined = " ".join(selected_impls.values()) + + engines = [ + { + "engine": "AES-NI / CPU INSTR", + "available": has_token(["aesni", "vaes"]), + "active": ("aesni" in selected_joined or "vaes" in selected_joined) + }, + { + "engine": "SIMD (AVX/NEON)", + "available": has_token(["avx", "sse", "simd", "neon"]), + "active": any(token in selected_joined for token in ["avx", "simd", "neon", "sse"]) + }, + { + "engine": "ARM CRYPTO EXT", + "available": has_token(["arm64", "ce", "neon"]), + "active": "arm64" in selected_joined + }, + { + "engine": "QAT OFFLOAD", + "available": has_token(["qat"]), + "active": "qat" in selected_joined + }, + { + "engine": "VIRTIO-CRYPTO", + "available": has_token(["virtio"]), + "active": "virtio" in selected_joined + } + ] + result = [] + for item in engines: + if item["active"]: + status = "active" + elif item["available"]: + status = "available" + else: + status = "unavailable" + result.append({ + "engine": item["engine"], + "status": status + }) + return result + +def collect_algorithm_requesters(items, kernel_clients): + """Infer likely requestor objects that trigger algorithm competition.""" + algo_map = {"aes": {}, "sha": {}, "chacha20": {}} + client_boost_rules = { + "aes": {"kTLS", "dm-crypt", "AF_ALG", "IPsec/XFRM"}, + "sha": {"kTLS", "AF_ALG", "IPsec/XFRM"}, + "chacha20": {"WireGuard", "AF_ALG"} + } + + for item in items or []: + process_name = str(item.get("process", "unknown")).lower() or "unknown" + protocol = str(item.get("protocol", "")).upper() + algorithm = str(item.get("algorithm", "")).upper() + status = str(item.get("status", "")).upper() + if status == "LISTEN": + continue + + matched_algorithms = set() + if "AES" in algorithm or protocol == "TLS": + matched_algorithms.add("aes") + if "SHA" in algorithm or protocol == "TLS": + matched_algorithms.add("sha") + if "CHACHA" in algorithm or protocol in {"WIREGUARD", "SSH"}: + matched_algorithms.add("chacha20") + if not matched_algorithms and protocol == "CRYPTO API": + matched_algorithms.update(["aes", "sha"]) + + for algo_key in matched_algorithms: + key = f"process:{process_name}" + bucket = algo_map[algo_key].setdefault(key, { + "name": process_name, + "kind": "process", + "score": 0 + }) + bucket["score"] += 1 + + for client in kernel_clients or []: + name = str(client.get("name", "")).strip() + flows = int(client.get("active_flows", 0) or 0) + if not name or flows <= 0: + continue + for algo_key, allowed_clients in client_boost_rules.items(): + if name not in allowed_clients: + continue + key = f"client:{name}" + bucket = algo_map[algo_key].setdefault(key, { + "name": name, + "kind": "kernel-client", + "score": 0 + }) + # Kernel clients are presented as primary requestor objects. + bucket["score"] += max(2, flows) + + result = {} + for algo_key, raw in algo_map.items(): + ranked = sorted(raw.values(), key=lambda x: x.get("score", 0), reverse=True) + if not ranked: + ranked = [{ + "name": "user/kernel request", + "kind": "generic", + "score": 1 + }] + result[algo_key] = ranked[:4] + return result + +def build_crypto_decision_pipelines(algorithm_competitions, kernel_clients, hw_offload, algorithm_requesters): + """Build visual decision pipeline metadata for each algorithm family.""" + hw_active = [h.get("engine") for h in (hw_offload or []) if h.get("status") == "active"] + hw_available = [h.get("engine") for h in (hw_offload or []) if h.get("status") == "available"] + capability_hint = ", ".join(hw_active[:2] or hw_available[:2]) if (hw_active or hw_available) else "generic-cpu-only" + + tfm_lookup_map = { + "AES": "crypto_alloc_skcipher(aes)", + "SHA": "crypto_alloc_shash(sha*)", + "CHACHA20": "crypto_alloc_skcipher(chacha20)" + } + + pipelines = {} + for key, comp in (algorithm_competitions or {}).items(): + request = str(comp.get("request", key)).upper() + impls = comp.get("implementations", []) or [] + shortlist = [str(x.get("name", "unknown")) for x in impls[:3]] + requesters = list((algorithm_requesters or {}).get(key, [])) + top_requester = requesters[0] if requesters else {"name": "user/kernel request", "kind": "generic"} + request_origin = f"{top_requester.get('kind', 'generic')}: {top_requester.get('name', 'unknown')}" + selected_driver = str((comp.get("selected") or {}).get("name", "unknown")) + fallback_driver = next((name for name in shortlist if "generic" in name.lower()), shortlist[-1] if shortlist else "none") + selected_is_generic = "generic" in selected_driver.lower() + selected_source = str((comp.get("selected") or {}).get("source", "kernel")).lower() + fallback_active = selected_is_generic and len(shortlist) > 1 + + pipelines[key] = { + "request": request, + "request_origin": request_origin, + "requesters": requesters, + "tfm_lookup": tfm_lookup_map.get(request, f"crypto_lookup({request.lower()})"), + "impl_shortlist": shortlist, + "priority_check": "max priority wins", + "capability_check": capability_hint, + "selected_driver": selected_driver, + "fallback_driver": fallback_driver, + "fallback_active": bool(fallback_active), + "fallback_reason": "higher-priority impl unavailable or unsupported" if fallback_active else "not-triggered", + "source": selected_source + } + return pipelines + def collect_crypto_realtime(): """ Build a near-realtime list of processes likely interacting with kernel crypto. @@ -3498,6 +3728,21 @@ def collect_crypto_realtime(): "sha": collect_algorithm_competition("sha"), "chacha20": collect_algorithm_competition("chacha20") } + proc_crypto_entries = parse_proc_crypto_entries() + kernel_clients = collect_kernel_crypto_clients(items) + hw_offload = collect_hw_offload_status(proc_crypto_entries, algorithm_competitions) + crypto_stage1 = { + "kernel_clients": kernel_clients, + "sync_async": collect_sync_async_queue(items), + "hw_offload": hw_offload + } + algorithm_requesters = collect_algorithm_requesters(items, kernel_clients) + crypto_decision_pipelines = build_crypto_decision_pipelines( + algorithm_competitions=algorithm_competitions, + kernel_clients=kernel_clients, + hw_offload=hw_offload, + algorithm_requesters=algorithm_requesters + ) return { "items": items[:24], @@ -3510,6 +3755,10 @@ def collect_crypto_realtime(): "tls_terminators": sorted(list(tls_listener_names))[:8], "algorithm_competition": algorithm_competitions["aes"], "algorithm_competitions": algorithm_competitions, + "algorithm_requesters": algorithm_requesters, + "crypto_stage1": crypto_stage1, + "crypto_decision_pipeline": crypto_decision_pipelines.get("aes", {}), + "crypto_decision_pipelines": crypto_decision_pipelines, "source": "live-heuristic-v2", "timestamp": datetime.utcnow().isoformat() + "Z" } diff --git a/index.html b/index.html index fb3efdb..ef2843c 100755 --- a/index.html +++ b/index.html @@ -96,11 +96,11 @@

Linux Kernel Ring 0 Visualization

- + - + @@ -121,6 +121,6 @@

Linux Kernel Ring 0 Visualization

// console.error('❌ Proc3DVisualization class NOT loaded!'); // } - + diff --git a/static/js/crypto-belt.js b/static/js/crypto-belt.js index 4284466..c3f782a 100644 --- a/static/js/crypto-belt.js +++ b/static/js/crypto-belt.js @@ -21,6 +21,7 @@ class CryptoSubsystemVisualization { this.recentlyGone = []; this.selectedCompetitionAlgorithm = 'AES'; this.algorithmModes = ['AES', 'SHA', 'CHACHA20']; + this.selectedClientFilters = new Set(); } init(containerId = 'crypto-belt-container') { @@ -102,7 +103,7 @@ class CryptoSubsystemVisualization { 'z-index: 1001', 'text-shadow: 0 0 8px rgba(180, 210, 255, 0.25)' ].join(';'); - title.textContent = 'CRYPTO LIVE INTERACTIONS (in development)'; + title.textContent = 'KERNEL CRYPTO LIVE INTERACTIONS (in development)'; this.container.appendChild(title); const subtitle = document.createElement('div'); @@ -270,6 +271,10 @@ class CryptoSubsystemVisualization { }; btn.onclick = () => { + const path = String(window.location.pathname || '').replace(/\/+$/, '') || '/'; + if (path === '/crypto' && window.history && typeof window.history.replaceState === 'function') { + window.history.replaceState({}, '', '/'); + } if (window.kernelContextMenu) { window.kernelContextMenu.deactivateViews(); } else { @@ -549,6 +554,26 @@ class CryptoSubsystemVisualization { }; } + getDecisionPipelinePayload(meta) { + const selected = String(this.selectedCompetitionAlgorithm || 'AES').toLowerCase(); + const groups = meta?.crypto_decision_pipelines || null; + if (groups && groups[selected]) return groups[selected]; + return meta?.crypto_decision_pipeline || { + request: this.selectedCompetitionAlgorithm, + request_origin: 'user/kernel request', + requesters: [{ name: 'user/kernel request', kind: 'generic', score: 1 }], + tfm_lookup: 'crypto_lookup(?)', + impl_shortlist: [], + priority_check: 'max priority wins', + capability_check: 'generic-cpu-only', + selected_driver: 'unknown', + fallback_driver: 'none', + fallback_active: false, + fallback_reason: 'not-triggered', + source: 'mock' + }; + } + drawAlgorithmCompetition(layer, meta, width) { const comp = this.getCompetitionPayload(meta); const request = String(comp.request || this.selectedCompetitionAlgorithm || 'AES').toUpperCase(); @@ -693,6 +718,312 @@ class CryptoSubsystemVisualization { }); } + drawDecisionPipeline(layer, meta, width, height) { + const pipeline = this.getDecisionPipelinePayload(meta); + const request = String(pipeline.request || this.selectedCompetitionAlgorithm || 'AES').toUpperCase(); + const shortlist = Array.isArray(pipeline.impl_shortlist) ? pipeline.impl_shortlist.slice(0, 3) : []; + const requesters = Array.isArray(pipeline.requesters) ? pipeline.requesters.slice(0, 3) : []; + const selectedDriver = String(pipeline.selected_driver || 'unknown'); + const fallbackDriver = String(pipeline.fallback_driver || 'none'); + const fallbackActive = Boolean(pipeline.fallback_active); + const panelX = Math.floor(width * 0.73); + const panelW = Math.max(260, Math.floor(width * 0.24)); + const panelY = Math.max(450, Math.floor(height * 0.52)); + const panelH = 278; + + const panel = layer.append('g').attr('class', 'crypto-decision-pipeline'); + panel.append('rect') + .attr('x', panelX) + .attr('y', panelY) + .attr('width', panelW) + .attr('height', panelH) + .attr('rx', 8) + .style('fill', 'rgba(8, 11, 16, 0.88)') + .style('stroke', 'rgba(165, 178, 200, 0.35)') + .style('stroke-width', 1); + + panel.append('text') + .attr('x', panelX + 14) + .attr('y', panelY + 20) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '11px') + .style('fill', '#d7ddea') + .text('CRYPTO DECISION PIPELINE'); + + panel.append('text') + .attr('x', panelX + 14) + .attr('y', panelY + 40) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', '#a8b7cd') + .text('requestors -> request'); + + if (!requesters.length) { + panel.append('text') + .attr('x', panelX + 14) + .attr('y', panelY + 56) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '9px') + .style('fill', '#8696ab') + .text('none detected'); + } else { + requesters.forEach((req, idx) => { + const reqName = String(req.name || 'unknown'); + const reqKind = String(req.kind || 'generic'); + const reqScore = Number(req.score || 0); + panel.append('text') + .attr('x', panelX + 14) + .attr('y', panelY + 56 + idx * 14) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '9px') + .style('fill', idx === 0 ? '#cce2ff' : '#95a6bc') + .text(`- ${reqName} [${reqKind}] (${reqScore})`); + }); + } + + const stepsBaseY = panelY + 102; + const lines = [ + `request (${request}) from ${String(pipeline.request_origin || 'user/kernel request')}`, + `tfm lookup: ${String(pipeline.tfm_lookup || 'crypto_lookup(?)')}`, + `impl shortlist: ${shortlist.length ? shortlist.join(' | ') : 'none'}`, + `priority check: ${String(pipeline.priority_check || 'max priority wins')}`, + `capability check: ${String(pipeline.capability_check || 'generic-cpu-only')}`, + `selected driver: ${selectedDriver}` + ]; + + lines.forEach((line, idx) => { + panel.append('text') + .attr('x', panelX + 14) + .attr('y', stepsBaseY + idx * 22) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', idx === 5 ? '#a4ffcf' : '#b8c3d4') + .text(line); + }); + + panel.append('text') + .attr('x', panelX + 14) + .attr('y', panelY + 240) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', fallbackActive ? '#ffb0b0' : '#95a6bc') + .text(`fallback: ${fallbackDriver} (${fallbackActive ? 'active' : 'not active'})`); + + panel.append('text') + .attr('x', panelX + 14) + .attr('y', panelY + 258) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '9px') + .style('fill', '#8393a8') + .text(`reason: ${String(pipeline.fallback_reason || 'not-triggered')}`); + } + + drawStage1Panels(layer, meta, width, height) { + const stage = meta?.crypto_stage1 || {}; + const clients = Array.isArray(stage.kernel_clients) ? stage.kernel_clients.slice(0, 6) : []; + const syncAsync = stage.sync_async || {}; + const offload = Array.isArray(stage.hw_offload) ? stage.hw_offload.slice(0, 5) : []; + + const panelW = Math.max(270, Math.floor(width * 0.23)); + const baseX = 26; + const gap = 12; + const clientsH = 134; + const queueH = 86; + const offloadH = 116; + const totalH = clientsH + queueH + offloadH + (gap * 2); + // Keep stage-1 HUD fully visible even on shorter viewports. + const baseY = Math.max(180, height - totalH - 20); + const isAllSelected = this.selectedClientFilters.size === 0; + + const drawPanelShell = (x, y, w, h, title) => { + const g = layer.append('g').attr('class', 'crypto-stage1-panel'); + g.append('rect') + .attr('x', x) + .attr('y', y) + .attr('width', w) + .attr('height', h) + .attr('rx', 8) + .style('fill', 'rgba(8, 11, 16, 0.84)') + .style('stroke', 'rgba(155, 168, 190, 0.32)') + .style('stroke-width', 1); + g.append('text') + .attr('x', x + 12) + .attr('y', y + 18) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', '#d2d9e6') + .text(title); + return g; + }; + + const clientsPanel = drawPanelShell(baseX, baseY, panelW, clientsH, 'KERNEL CRYPTO CLIENTS'); + const resetGroup = clientsPanel.append('g') + .style('cursor', 'pointer') + .on('click', () => { + this.selectedClientFilters.clear(); + this.renderFlowMap(this.lastPayload || this.normalizeTelemetry(this.getFallbackTelemetry())); + }); + resetGroup.append('rect') + .attr('x', baseX + panelW - 56) + .attr('y', baseY + 7) + .attr('width', 42) + .attr('height', 14) + .attr('rx', 4) + .style('fill', isAllSelected ? 'rgba(40, 66, 100, 0.9)' : 'rgba(13, 18, 24, 0.82)') + .style('stroke', isAllSelected ? 'rgba(129, 180, 255, 0.9)' : 'rgba(150, 164, 184, 0.3)') + .style('stroke-width', 0.8); + resetGroup.append('text') + .attr('x', baseX + panelW - 35) + .attr('y', baseY + 17) + .attr('text-anchor', 'middle') + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '9px') + .style('fill', isAllSelected ? '#d3e7ff' : '#9aa9bc') + .text('ALL'); + + clientsPanel.append('text') + .attr('x', baseX + panelW - 96) + .attr('y', baseY + 34) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '8px') + .style('fill', '#7f8ea4') + .text('multi-select'); + + if (!clients.length) { + clientsPanel.append('text') + .attr('x', baseX + 12) + .attr('y', baseY + 40) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', '#95a4b7') + .text('No active clients'); + } else { + clients.forEach((item, idx) => { + const y = baseY + 36 + idx * 16; + const status = String(item.status || 'idle').toLowerCase(); + const dotColor = status === 'active' ? '#8effc8' : '#8f9caf'; + const itemName = String(item.name || ''); + const isActiveFilter = this.selectedClientFilters.has(itemName); + const row = clientsPanel.append('g') + .style('cursor', 'pointer') + .on('click', () => { + if (this.selectedClientFilters.has(itemName)) { + this.selectedClientFilters.delete(itemName); + } else { + this.selectedClientFilters.add(itemName); + } + this.renderFlowMap(this.lastPayload || this.normalizeTelemetry(this.getFallbackTelemetry())); + }); + row.append('rect') + .attr('x', baseX + 8) + .attr('y', y - 12) + .attr('width', panelW - 16) + .attr('height', 14) + .attr('rx', 3) + .style('fill', isActiveFilter ? 'rgba(37, 58, 92, 0.62)' : 'transparent') + .style('stroke', isActiveFilter ? 'rgba(120, 170, 245, 0.72)' : 'transparent') + .style('stroke-width', 0.8); + row.append('circle') + .attr('cx', baseX + 14) + .attr('cy', y - 3) + .attr('r', 2.8) + .style('fill', dotColor); + row.append('text') + .attr('x', baseX + 22) + .attr('y', y) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', isActiveFilter ? '#d8e7ff' : '#b7c2d3') + .text(`${itemName}: ${status} (${Number(item.active_flows || 0)})`); + }); + } + + const queueY = baseY + clientsH + gap; + const queuePanel = drawPanelShell(baseX, queueY, panelW, queueH, 'SYNC VS ASYNC QUEUE'); + const syncOps = Number(syncAsync.sync_ops_est || 0); + const asyncOps = Number(syncAsync.async_ops_est || 0); + const qDepth = Number(syncAsync.queue_depth_est || 0); + const qLat = Number(syncAsync.queue_latency_ms_est || 0); + queuePanel.append('text') + .attr('x', baseX + 12) + .attr('y', queueY + 38) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', '#aeb9ca') + .text(`sync:${syncOps} async:${asyncOps} depth:${qDepth}`); + queuePanel.append('text') + .attr('x', baseX + 12) + .attr('y', queueY + 56) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', '#aeb9ca') + .text(`queue latency est: ${qLat.toFixed(2)} ms`); + + const offloadY = queueY + queueH + gap; + const offloadPanel = drawPanelShell(baseX, offloadY, panelW, offloadH, 'HW OFFLOAD STATUS'); + if (!offload.length) { + offloadPanel.append('text') + .attr('x', baseX + 12) + .attr('y', offloadY + 38) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', '#95a4b7') + .text('No offload providers detected'); + } else { + offload.forEach((item, idx) => { + const y = offloadY + 34 + idx * 16; + const status = String(item.status || 'unavailable').toLowerCase(); + const color = status === 'active' + ? '#8effc8' + : (status === 'available' ? '#ffe39f' : '#9aa7b9'); + offloadPanel.append('text') + .attr('x', baseX + 12) + .attr('y', y) + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '10px') + .style('fill', color) + .text(`${item.engine}: ${status}`); + }); + } + } + + laneMatchesClient(clientName, lane) { + const client = String(clientName || '').toLowerCase(); + if (!client || client === 'all') return true; + const process = String(lane?.process || '').toLowerCase(); + const protocol = String(lane?.protocol || '').toUpperCase(); + const algo = String(lane?.algorithm || '').toLowerCase(); + const sourceKind = String(lane?.source_kind || '').toLowerCase(); + + if (client === 'ktls') { + return protocol === 'TLS' || ['nginx', 'haproxy', 'envoy', 'caddy', 'apache', 'httpd', 'traefik'].some((x) => process.includes(x)); + } + if (client === 'wireguard') { + return protocol === 'WIREGUARD' || process.includes('wg') || process.includes('wireguard'); + } + if (client === 'ipsec/xfrm') { + return process.includes('ipsec') || process.includes('strongswan') || process.includes('charon') || process.includes('racoon'); + } + if (client === 'dm-crypt') { + return process.includes('crypt') || process.includes('luks') || sourceKind === 'process'; + } + if (client === 'fscrypt') { + return process.includes('fscrypt'); + } + if (client === 'af_alg') { + return ['openssl', 'python', 'curl', 'wget'].some((x) => process.includes(x)) || sourceKind === 'connection'; + } + return true; + } + + laneMatchesSelectedClients(lane) { + if (!this.selectedClientFilters.size) return true; + for (const clientName of this.selectedClientFilters) { + if (this.laneMatchesClient(clientName, lane)) return true; + } + return false; + } + drawNode(group, x, y, label, level, intensity, palette, emphasis) { const width = Math.min(Math.max(150, String(label).length * 8 + 28), 250); const height = 34; @@ -794,8 +1125,11 @@ class CryptoSubsystemVisualization { this.drawGrid(layer, width, height); this.drawProtocolLegend(layer); this.drawAlgorithmCompetition(layer, payload?.meta || {}, width); + this.drawDecisionPipeline(layer, payload?.meta || {}, width, height); + this.drawStage1Panels(layer, payload?.meta || {}, width, height); - const lanes = Array.isArray(payload.items) ? payload.items : []; + const sourceLanes = Array.isArray(payload.items) ? payload.items : []; + const lanes = sourceLanes.filter((lane) => this.laneMatchesSelectedClients(lane)); const topY = 150; const protocolY = 250; const cryptoY = 350; @@ -807,6 +1141,20 @@ class CryptoSubsystemVisualization { const laneCount = Math.max(lanes.length, 1); const laneStep = laneCount > 1 ? usableWidth / (laneCount - 1) : 0; + if (!lanes.length) { + const selectedLabel = this.selectedClientFilters.size + ? Array.from(this.selectedClientFilters).join(' + ') + : 'ALL'; + layer.append('text') + .attr('x', width * 0.42) + .attr('y', 320) + .attr('text-anchor', 'middle') + .style('font-family', 'Share Tech Mono, monospace') + .style('font-size', '12px') + .style('fill', '#8fa0b6') + .text(`NO ACTIVE PATHS FOR ${selectedLabel.toUpperCase()}`); + } + lanes.forEach((lane, idx) => { const x = startX + laneStep * idx; const intensity = Math.min(1 + lane.weight * 0.35, 2.2); @@ -873,7 +1221,9 @@ class CryptoSubsystemVisualization { .style('font-family', 'Share Tech Mono, monospace') .style('font-size', '11px') .style('fill', '#d2d9e5') - .text('ACTIVE PATHS'); + .text(this.selectedClientFilters.size + ? `ACTIVE PATHS (${Array.from(this.selectedClientFilters).join(' + ')})` + : 'ACTIVE PATHS'); lanes.slice(0, 8).forEach((lane, idx) => { legend.append('text') @@ -959,6 +1309,120 @@ class CryptoSubsystemVisualization { selection_policy: 'max-priority' } }, + crypto_stage1: { + kernel_clients: [ + { name: 'kTLS', status: 'active', active_flows: 2 }, + { name: 'WireGuard', status: 'idle', active_flows: 0 }, + { name: 'IPsec/XFRM', status: 'idle', active_flows: 0 }, + { name: 'dm-crypt', status: 'active', active_flows: 1 }, + { name: 'fscrypt', status: 'idle', active_flows: 0 }, + { name: 'AF_ALG', status: 'active', active_flows: 1 } + ], + sync_async: { + sync_ops_est: 3, + async_ops_est: 2, + queue_depth_est: 1, + queue_latency_ms_est: 1.28 + }, + hw_offload: [ + { engine: 'AES-NI / CPU INSTR', status: 'active' }, + { engine: 'SIMD (AVX/NEON)', status: 'available' }, + { engine: 'ARM CRYPTO EXT', status: 'unavailable' }, + { engine: 'QAT OFFLOAD', status: 'unavailable' }, + { engine: 'VIRTIO-CRYPTO', status: 'unavailable' } + ] + }, + crypto_decision_pipeline: { + request: 'AES', + request_origin: 'kernel client: kTLS', + requesters: [ + { name: 'kTLS', kind: 'kernel-client', score: 3 }, + { name: 'nginx', kind: 'process', score: 2 }, + { name: 'AF_ALG', kind: 'kernel-client', score: 1 } + ], + tfm_lookup: 'crypto_alloc_skcipher(aes)', + impl_shortlist: ['aesni-intel', 'aes-avx', 'aes-generic'], + priority_check: 'max priority wins', + capability_check: 'AES-NI / CPU INSTR, SIMD (AVX/NEON)', + selected_driver: 'aesni-intel', + fallback_driver: 'aes-generic', + fallback_active: false, + fallback_reason: 'not-triggered', + source: 'mock' + }, + crypto_decision_pipelines: { + aes: { + request: 'AES', + request_origin: 'kernel client: kTLS', + requesters: [ + { name: 'kTLS', kind: 'kernel-client', score: 3 }, + { name: 'nginx', kind: 'process', score: 2 }, + { name: 'AF_ALG', kind: 'kernel-client', score: 1 } + ], + tfm_lookup: 'crypto_alloc_skcipher(aes)', + impl_shortlist: ['aesni-intel', 'aes-avx', 'aes-generic'], + priority_check: 'max priority wins', + capability_check: 'AES-NI / CPU INSTR, SIMD (AVX/NEON)', + selected_driver: 'aesni-intel', + fallback_driver: 'aes-generic', + fallback_active: false, + fallback_reason: 'not-triggered', + source: 'mock' + }, + sha: { + request: 'SHA', + request_origin: 'kernel client: AF_ALG', + requesters: [ + { name: 'AF_ALG', kind: 'kernel-client', score: 2 }, + { name: 'kTLS', kind: 'kernel-client', score: 1 }, + { name: 'nginx', kind: 'process', score: 1 } + ], + tfm_lookup: 'crypto_alloc_shash(sha*)', + impl_shortlist: ['sha256-avx2', 'sha256-ssse3', 'sha256-generic'], + priority_check: 'max priority wins', + capability_check: 'SIMD (AVX/NEON)', + selected_driver: 'sha256-avx2', + fallback_driver: 'sha256-generic', + fallback_active: false, + fallback_reason: 'not-triggered', + source: 'mock' + }, + chacha20: { + request: 'CHACHA20', + request_origin: 'kernel client: WireGuard', + requesters: [ + { name: 'WireGuard', kind: 'kernel-client', score: 2 }, + { name: 'sshd', kind: 'process', score: 1 }, + { name: 'AF_ALG', kind: 'kernel-client', score: 1 } + ], + tfm_lookup: 'crypto_alloc_skcipher(chacha20)', + impl_shortlist: ['chacha20-neon', 'chacha20-simd', 'chacha20-generic'], + priority_check: 'max priority wins', + capability_check: 'SIMD (AVX/NEON)', + selected_driver: 'chacha20-neon', + fallback_driver: 'chacha20-generic', + fallback_active: false, + fallback_reason: 'not-triggered', + source: 'mock' + } + }, + algorithm_requesters: { + aes: [ + { name: 'kTLS', kind: 'kernel-client', score: 3 }, + { name: 'nginx', kind: 'process', score: 2 }, + { name: 'AF_ALG', kind: 'kernel-client', score: 1 } + ], + sha: [ + { name: 'AF_ALG', kind: 'kernel-client', score: 2 }, + { name: 'kTLS', kind: 'kernel-client', score: 1 }, + { name: 'nginx', kind: 'process', score: 1 } + ], + chacha20: [ + { name: 'WireGuard', kind: 'kernel-client', score: 2 }, + { name: 'sshd', kind: 'process', score: 1 }, + { name: 'AF_ALG', kind: 'kernel-client', score: 1 } + ] + }, source: 'mock' } }; diff --git a/static/js/main.js b/static/js/main.js index c680823..b7c7f4a 100755 --- a/static/js/main.js +++ b/static/js/main.js @@ -91,6 +91,15 @@ function initApp() { // Setup event handlers setupEventListeners(); + + // When nginx serves SPA fallback (/index.html) for /crypto, + // open the dedicated crypto view automatically by route. + const path = String(window.location.pathname || '').replace(/\/+$/, '') || '/'; + if (path === '/crypto' && window.kernelContextMenu && typeof window.kernelContextMenu.activateCryptoView === 'function') { + setTimeout(() => { + window.kernelContextMenu.activateCryptoView(); + }, 140); + } } // Setup event handlers diff --git a/static/js/right-semicircle-menu.js b/static/js/right-semicircle-menu.js index 49abc9a..2e452c6 100644 --- a/static/js/right-semicircle-menu.js +++ b/static/js/right-semicircle-menu.js @@ -74,10 +74,13 @@ class RightSemicircleMenuManager { } activateOverlayView(itemId) { - if (!window.kernelContextMenu) return; if (itemId === 'scheduler') { - window.kernelContextMenu.activateCryptoView(); - } else if (itemId === 'kernel') { + // Force hard navigation to dedicated SEO page. + window.location.assign('/crypto'); + return; + } + if (!window.kernelContextMenu) return; + if (itemId === 'kernel') { window.kernelContextMenu.activateDNAView(); } else if (itemId === 'network') { window.kernelContextMenu.activateNetworkView(); @@ -344,6 +347,10 @@ class RightSemicircleMenuManager { const handleClick = (event) => { debugLog('🖱️ Click detected on item:', item.id, item.label); event.stopPropagation(); // Prevent event bubbling + if (item.id === 'scheduler') { + window.location.assign('/crypto'); + return; + } if (isOverlay) { this.activateOverlayView(item.id); } else { @@ -360,6 +367,10 @@ class RightSemicircleMenuManager { debugLog('🖱️ Mouse down on overlay item:', item.id); event.preventDefault(); event.stopPropagation(); + if (item.id === 'scheduler') { + window.location.assign('/crypto'); + return; + } // Prevent re-rendering during click this.isClickingOverlay = true; this.activateOverlayView(item.id);