Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ <h2>Linux Kernel Ring 0 Visualization</h2>
<script src="/static/js/isolation-ui.js?v=10"></script>
<script src="/static/js/process-files-ui.js?v=1"></script>
<script src="/static/js/ui-chrome.js?v=2"></script>
<script src="/static/js/active-connections.js?v=4"></script>
<script src="/static/js/active-connections.js?v=5"></script>
<script src="/static/js/nginx_files.js?v=6"></script>
<script src="/static/js/right-semicircle-menu.js?v=51"></script>
<script src="/static/js/kernel-dna.js?v=49"></script>
Expand Down
150 changes: 142 additions & 8 deletions kernel_ai/services/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import ipaddress
import logging
import os
import re
import subprocess
import time
Expand Down Expand Up @@ -55,7 +56,7 @@
lines = f.readlines()[1:]
for line in lines:
parts = line.strip().split()
if len(parts) < 4:
if len(parts) < 10:
continue
local_addr = parts[1]
remote_addr = parts[2]
Expand All @@ -68,6 +69,14 @@

local_ip = hex_to_ip(local_addr.split(":")[0])
local_port = int(local_addr.split(":")[1], 16)
try:
uid = int(parts[7])
except ValueError:
uid = 0
try:
inode = int(parts[9])
except ValueError:
inode = 0

if remote_addr != "00000000:0000":
remote_ip = hex_to_ip(remote_addr.split(":")[0])
Expand All @@ -78,6 +87,8 @@
"remote": f"{remote_ip}:{remote_port}",
"state": state,
"type": "TCP",
"uid": uid,
"inode": inode,
}
)
return connections[:20]
Expand All @@ -97,14 +108,78 @@

def get_mock_active_connections():
return [
{"local": "127.0.0.1:22", "remote": "192.168.1.100:54321", "state": "01", "type": "TCP"},
{"local": "0.0.0.0:80", "remote": "10.0.0.50:12345", "state": "01", "type": "TCP"},
{"local": "127.0.0.1:3306", "remote": "172.16.0.10:65432", "state": "01", "type": "TCP"},
{"local": "0.0.0.0:443", "remote": "203.0.113.0:54321", "state": "01", "type": "TCP"},
{"local": "127.0.0.1:5001", "remote": "192.168.1.101:12345", "state": "01", "type": "TCP"},
{"local": "127.0.0.1:22", "remote": "192.168.1.100:54321", "state": "01", "type": "TCP", "uid": 0, "inode": 12345},

Check warning on line 111 in kernel_ai/services/network.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make sure using this hardcoded IP address "192.168.1.100" is safe here.

See more on https://sonarcloud.io/project/issues?id=devops-stack_ring-0&issues=AZ-dZy8xiXpRjQDkgHKM&open=AZ-dZy8xiXpRjQDkgHKM&pullRequest=149
{"local": "0.0.0.0:80", "remote": "10.0.0.50:12345", "state": "01", "type": "TCP", "uid": 0, "inode": 12346},

Check warning on line 112 in kernel_ai/services/network.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make sure using this hardcoded IP address "10.0.0.50" is safe here.

See more on https://sonarcloud.io/project/issues?id=devops-stack_ring-0&issues=AZ-dZy8xiXpRjQDkgHKN&open=AZ-dZy8xiXpRjQDkgHKN&pullRequest=149
{"local": "127.0.0.1:3306", "remote": "172.16.0.10:65432", "state": "01", "type": "TCP", "uid": 0, "inode": 12347},

Check warning on line 113 in kernel_ai/services/network.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make sure using this hardcoded IP address "172.16.0.10" is safe here.

See more on https://sonarcloud.io/project/issues?id=devops-stack_ring-0&issues=AZ-dZy8xiXpRjQDkgHKO&open=AZ-dZy8xiXpRjQDkgHKO&pullRequest=149
{"local": "0.0.0.0:443", "remote": "203.0.113.0:54321", "state": "01", "type": "TCP", "uid": 0, "inode": 12348},
{"local": "127.0.0.1:5001", "remote": "192.168.1.101:12345", "state": "01", "type": "TCP", "uid": 0, "inode": 12349},

Check warning on line 115 in kernel_ai/services/network.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make sure using this hardcoded IP address "192.168.1.101" is safe here.

See more on https://sonarcloud.io/project/issues?id=devops-stack_ring-0&issues=AZ-dZy8xiXpRjQDkgHKP&open=AZ-dZy8xiXpRjQDkgHKP&pullRequest=149
]


def _addr_key(addr) -> str:
if not addr:
return ""
if isinstance(addr, (tuple, list)) and len(addr) >= 2:
return f"{addr[0]}:{addr[1]}"
return str(addr)


def _get_socket_example(all_connections: list) -> dict | None:

Check failure on line 127 in kernel_ai/services/network.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 42 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=devops-stack_ring-0&issues=AZ-dZy8xiXpRjQDkgHKQ&open=AZ-dZy8xiXpRjQDkgHKQ&pullRequest=149
"""Pick a live flow and attach fd/pid via psutil when possible (for Socket morph)."""
interesting = [
c
for c in all_connections
if not str(c.get("remote", "")).startswith("127.0.0.1")
and not str(c.get("remote", "")).startswith("0.0.0.0")
]
# Prefer ESTABLISHED with a real inode — better for fd→sock* morph.
established = [
c for c in interesting
if str(c.get("state", "")).upper() == "01" and int(c.get("inode") or 0) > 0
]
with_inode = [c for c in interesting if int(c.get("inode") or 0) > 0]
base = (
established[0]
if established
else (with_inode[0] if with_inode else (interesting[0] if interesting else (all_connections[0] if all_connections else None)))

Check warning on line 144 in kernel_ai/services/network.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested conditional expression into an independent statement.

See more on https://sonarcloud.io/project/issues?id=devops-stack_ring-0&issues=AZ-dZy8xiXpRjQDkgHKR&open=AZ-dZy8xiXpRjQDkgHKR&pullRequest=149

Check warning on line 144 in kernel_ai/services/network.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested conditional expression into an independent statement.

See more on https://sonarcloud.io/project/issues?id=devops-stack_ring-0&issues=AZ-dZy8xiXpRjQDkgHKT&open=AZ-dZy8xiXpRjQDkgHKT&pullRequest=149

Check warning on line 144 in kernel_ai/services/network.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested conditional expression into an independent statement.

See more on https://sonarcloud.io/project/issues?id=devops-stack_ring-0&issues=AZ-dZy8xiXpRjQDkgHKS&open=AZ-dZy8xiXpRjQDkgHKS&pullRequest=149
)
if not base:
return None
example = {
"local": base.get("local"),
"remote": base.get("remote"),
"type": str(base.get("type", "TCP")).upper(),
"state_code": base.get("state", "00"),
"state_name": _tcp_state_name(base.get("state", "00")),
"inode": int(base.get("inode") or 0),
"uid": int(base.get("uid") or 0),
"fd": None,
"pid": None,
"process": None,
}
want_local = str(example["local"] or "")
want_remote = str(example["remote"] or "")
try:
for conn in psutil.net_connections(kind="inet"):
if conn.status and str(conn.status).upper() not in ("ESTABLISHED", "LISTEN", "CLOSE_WAIT", "TIME_WAIT"):
# Still allow match by address even for other states.
pass
local = _addr_key(conn.laddr)
remote = _addr_key(conn.raddr)
if local == want_local and (not want_remote or remote == want_remote or not remote):
example["fd"] = int(conn.fd) if conn.fd is not None and conn.fd >= 0 else None
example["pid"] = int(conn.pid) if conn.pid is not None else None
if example["pid"]:
try:
example["process"] = psutil.Process(example["pid"]).name()
except (psutil.Error, OSError):
example["process"] = None
break
except (psutil.Error, OSError, AttributeError):
pass
return example


def _tcp_state_name(code):
states = {
"01": "ESTABLISHED",
Expand Down Expand Up @@ -282,6 +357,44 @@
return v * scale


def _read_sysctl_int(path: str, default: int = 0) -> int:
try:
with open(path, "r", encoding="utf-8", errors="ignore") as fh:
return int(fh.read().strip())
except (OSError, ValueError):
return default


def _get_conntrack_stats() -> dict:
"""Best-effort nf_conntrack occupancy for the Netfilter morph view."""
count = _read_sysctl_int("/proc/sys/net/netfilter/nf_conntrack_count", 0)
maximum = _read_sysctl_int("/proc/sys/net/netfilter/nf_conntrack_max", 0)
available = count > 0 or maximum > 0
# Also true when the sysctl node exists even at zero.
try:
available = available or os.path.exists("/proc/sys/net/netfilter/nf_conntrack_count")
except OSError:
pass
usage = round(count / maximum, 5) if maximum > 0 else 0.0
nf_conntrack = False
nft = False
try:
with open("/proc/modules", "r", encoding="utf-8", errors="ignore") as fh:
mods = fh.read()
nf_conntrack = "nf_conntrack" in mods
nft = "nf_tables" in mods
except OSError:
pass
return {
"available": bool(available),
"count": int(count),
"max": int(maximum),
"usage": usage,
"nf_conntrack": nf_conntrack,
"nft": nft,
}


def _hex_le_ipv4(hex_str: str) -> str:
raw = (hex_str or "").strip()
if len(raw) != 8:
Expand Down Expand Up @@ -408,19 +521,26 @@
all_connections = get_active_connections()
interesting = [c for c in all_connections if not c["remote"].startswith("127.0.0.1") and not c["remote"].startswith("0.0.0.0")]
flow = interesting[0] if interesting else (all_connections[0] if all_connections else None)
socket_example = _get_socket_example(all_connections)
if flow:
flow = {
"local": flow.get("local"),
"remote": flow.get("remote"),
"type": str(flow.get("type", "TCP")).upper(),
"state_code": flow.get("state", "00"),
"state_name": _tcp_state_name(flow.get("state", "00")),
"inode": int(flow.get("inode") or 0),
"uid": int(flow.get("uid") or 0),
"fd": (socket_example or {}).get("fd"),
"pid": (socket_example or {}).get("pid"),
"process": (socket_example or {}).get("process"),
}

tcpext = _parse_netstat_tcpext()
ip_stats = _parse_snmp_section("Ip")
tcp_stats = _parse_snmp_section("Tcp")
ss_metrics = _get_ss_tcp_metrics()
conntrack = _get_conntrack_stats()

retrans_total = tcpext.get("RetransSegs", 0)
ip_in_total = ip_stats.get("InReceives", 0)
Expand Down Expand Up @@ -492,7 +612,12 @@
"flow": flow,
"layer_metrics": {
"userspace": {"active_processes": len(psutil.pids())},
"socket_api": {"active_sockets": len(all_connections), "established": established, "retransmits_per_sec": round(retrans_per_sec, 2)},
"socket_api": {
"active_sockets": len(all_connections),
"established": established,
"retransmits_per_sec": round(retrans_per_sec, 2),
"example": socket_example,
},
"tcp_udp": {
"established": established,
"retrans_per_sec": round(retrans_per_sec, 2),
Expand All @@ -509,7 +634,16 @@
"drop_per_sec": round(ip_drop_per_sec, 3),
"drop_ratio": round(drop_ratio, 5),
},
"netfilter": {"drop_per_sec": round(ip_drop_per_sec, 3), "drop_ratio": round(drop_ratio, 5)},
"netfilter": {
"drop_per_sec": round(ip_drop_per_sec, 3),
"drop_ratio": round(drop_ratio, 5),
"conntrack_count": int(conntrack.get("count", 0)),
"conntrack_max": int(conntrack.get("max", 0)),
"conntrack_usage": float(conntrack.get("usage", 0.0)),
"conntrack_available": bool(conntrack.get("available")),
"nft": bool(conntrack.get("nft")),
"nf_conntrack": bool(conntrack.get("nf_conntrack")),
},
"driver": {
"iface": iface,
"rx_mb_s": round(rx_per_sec / (1024 * 1024), 3),
Expand Down
14 changes: 14 additions & 0 deletions static/js/active-connections.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Module for working with active network connections
class ActiveConnectionsManager {
constructor() {
debugLog("ActiveConnectionsManager: constructor called");

Check failure on line 4 in static/js/active-connections.js

View workflow job for this annotation

GitHub Actions / security

'debugLog' is not defined
this.currentConnections = [];
this.updateInterval = null;
this.updateCallback = null;
Expand All @@ -12,14 +12,14 @@
// Update active connections data
async updateConnectionsTable() {
try {
debugLog("ActiveConnectionsManager: updateConnectionsTable called");

Check failure on line 15 in static/js/active-connections.js

View workflow job for this annotation

GitHub Actions / security

'debugLog' is not defined
const response = await fetch('/api/active-connections');
const data = await response.json();

debugLog("API response:", data);

Check failure on line 19 in static/js/active-connections.js

View workflow job for this annotation

GitHub Actions / security

'debugLog' is not defined

if (data.connections) {
debugLog("Total connections:", data.connections.length);

Check failure on line 22 in static/js/active-connections.js

View workflow job for this annotation

GitHub Actions / security

'debugLog' is not defined

// Filter out local connections (127.0.0.1, 0.0.0.0)
const filteredConnections = data.connections.filter(conn => {
Expand All @@ -32,8 +32,8 @@

this.currentConnections = filteredConnections;

debugLog("Filtered connections:", this.currentConnections.length);

Check failure on line 35 in static/js/active-connections.js

View workflow job for this annotation

GitHub Actions / security

'debugLog' is not defined
debugLog("Filtered out:", data.connections.length - this.currentConnections.length, "local connections");

Check failure on line 36 in static/js/active-connections.js

View workflow job for this annotation

GitHub Actions / security

'debugLog' is not defined

this.renderConnectionsTable();

Expand All @@ -44,7 +44,7 @@
}
} catch (error) {
console.error('Error getting active connections:', error);
debugLog('Using fallback data...');

Check failure on line 47 in static/js/active-connections.js

View workflow job for this annotation

GitHub Actions / security

'debugLog' is not defined
this.useFallbackData();
}
}
Expand All @@ -67,11 +67,11 @@
renderConnectionsTable() {
// Do not render connections if Kernel Matrix View is active
if (window.kernelContextMenu && window.kernelContextMenu.currentView === 'matrix') {
debugLog('⏸️ Skipping active connections render - Matrix View is active');

Check failure on line 70 in static/js/active-connections.js

View workflow job for this annotation

GitHub Actions / security

'debugLog' is not defined
return;
}

const svg = d3.select('svg');

Check failure on line 74 in static/js/active-connections.js

View workflow job for this annotation

GitHub Actions / security

'd3' is not defined

// Clear old elements
svg.selectAll('.connection-row, .connection-box, .connection-text, .connection-details, .connection-header').remove();
Expand Down Expand Up @@ -117,6 +117,20 @@
.attr('font-family', 'monospace');

this.attachSocketTooltipHandlers(row, connection);

// Flow follow: open Network stack focused on this remote in FIB/neigh.
row.on('click', (event) => {
if (event && typeof event.stopPropagation === 'function') event.stopPropagation();
const remote = String(connection.remote || '').trim();
if (!remote) return;
const local = String(connection.local || '').trim();
const type = String(connection.type || 'TCP').toUpperCase();
const params = new URLSearchParams();
params.set('remote', remote);
if (local) params.set('local', local);
if (type) params.set('proto', type);
window.location.assign(`/linux-network-subsystem?${params.toString()}`);
});
});
}

Expand Down Expand Up @@ -149,7 +163,7 @@
const normalizedProtocol = String(protocol || "").toUpperCase();
const normalizedState = String(stateCode).toUpperCase();
if (normalizedProtocol.includes("UDP")) return "DATAGRAM";
return tcpStates[normalizedState] || normalizedState;

Check warning on line 166 in static/js/active-connections.js

View workflow job for this annotation

GitHub Actions / security

Generic Object Injection Sink
}

buildSocketTooltipHtml(connection) {
Expand Down Expand Up @@ -216,7 +230,7 @@
attachSocketTooltipHandlers(selection, connection) {
selection
.on("mouseover", (event) => {
d3.selectAll(".socket-tooltip").remove();

Check failure on line 233 in static/js/active-connections.js

View workflow job for this annotation

GitHub Actions / security

'd3' is not defined
const tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip socket-tooltip")
Expand Down
Loading
Loading