nx_os_introspect.nx source
↩ module page · 203 lines · 9957 B
1// nx_os_introspect.nx -- OS "OBSERVE THE RUNNING SYSTEM" library (CAP-OS-INTROSPECT / CAP-PORT-OWNER).
2// The sovereign /proc perception rung (axis=os, owner=CONDUCTOR, parent VAR-OS-NISHI): read the process + socket
3// tables via SYSCALLS ONLY (no ps/ss/lsof/netstat) to answer "what process really holds this port?". EXTRACTED
4// (un-trapped) from nx_portcheck.nx's GREEN 5/5 chain so it is IMPORTABLE -- the shared substrate BOTH the live
5// mgmt plane (nx_hostctl snapshot -> /api/services -> /health) AND the hardware-up digital twin (a twin must
6// introspect the running system it models) compose, instead of each re-implementing the /proc walk (the sprawl
7// this retires: nx_socket_list stubbed the very inode->PID step this finishes). NO main() -> pure library.
8// Chain: /proc/net/tcp{,6} LISTEN(0x0A) -> inode ; /proc/<pid>/fd socket:[inode] -> PID ; /proc/<pid>/{comm,cmdline}.
9// license_tier: ORIGINAL
10import "nx_syscalls.nx"
11const PON_MAGIC_32768: i64 = 32768
12
13const PON_TCP_LISTEN: i64 = 0x0A
14const PON_AT_FDCWD: i64 = 0 - 100
15const PON_SYS_READLINKAT: i64 = 267 // x86_64 readlinkat(dirfd, path, buf, bufsiz)
16
17func pon_is_space(c: i64) -> i64 { if c == 32 { return 1 } if c == 9 { return 1 } return 0 }
18func pon_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
19
20// parse a hex run [s,e) -> value (ports in /proc/net/tcp are stored big-endian hex text, compared directly).
21func pon_hex(buf: *u8, s: i64, e: i64) -> i64 {
22 var v: i64 = 0; var i: i64 = s
23 while i < e {
24 let c: i64 = buf[i] & 0xff; var d: i64 = 0 - 1
25 if c >= 48 { if c <= 57 { d = c - 48 } }
26 if c >= 65 { if c <= 70 { d = c - 55 } }
27 if c >= 97 { if c <= 102 { d = c - 87 } }
28 if d < 0 { return v }
29 v = (v << 4) | d; i = i + 1
30 }
31 return v
32}
33// parse a decimal run [s,e) -> value
34func pon_dec(buf: *u8, s: i64, e: i64) -> i64 {
35 var v: i64 = 0; var i: i64 = s
36 while i < e { let c: i64 = buf[i] & 0xff; if c < 48 { return v } if c > 57 { return v } v = v*10 + (c-48); i = i + 1 }
37 return v
38}
39// whitespace-delimited field idx in [lstart,lend) -> fs/fe = its [start,end). returns 1/0.
40func pon_field(buf: *u8, lstart: i64, lend: i64, idx: i64, fs: *i64, fe: *i64) -> i64 {
41 var o: i64 = lstart; var f: i64 = 0
42 while o < lend {
43 while o < lend { if pon_is_space(buf[o] as i64) == 1 { o = o + 1 } else { break } }
44 if o >= lend { return 0 }
45 let st: i64 = o
46 while o < lend { if pon_is_space(buf[o] as i64) == 1 { break } else { o = o + 1 } }
47 if f == idx { fs[0] = st; fe[0] = o; return 1 }
48 f = f + 1
49 }
50 return 0
51}
52// scan a /proc/net/tcp* file for a LISTEN socket on `port`; return its inode (0 = none).
53func pon_scan_file(path: *u8, port: i64) -> i64 {
54 let lb: *i64 = sys_mmap(8) as *i64
55 let data: *u8 = sys_read_file(path, lb)
56 if (data as i64) == 0 { return 0 }
57 let n: i64 = lb[0]
58 let fs: *i64 = sys_mmap(8) as *i64; let fe: *i64 = sys_mmap(8) as *i64
59 var ls: i64 = 0; var first: i64 = 1
60 while ls < n {
61 var le: i64 = ls
62 while le < n { if data[le] == (10 as u8) { break } else { le = le + 1 } }
63 if first == 1 { first = 0 }
64 else { if le > ls {
65 if pon_field(data, ls, le, 3, fs, fe) == 1 { // st
66 if pon_hex(data, fs[0], fe[0]) == PON_TCP_LISTEN {
67 if pon_field(data, ls, le, 1, fs, fe) == 1 { // local_address "IP:PORT"
68 var cp: i64 = fs[0]
69 while cp < fe[0] { if data[cp] == (58 as u8) { break } else { cp = cp + 1 } } // ':'
70 if cp < fe[0] {
71 if pon_hex(data, cp + 1, fe[0]) == port {
72 if pon_field(data, ls, le, 9, fs, fe) == 1 { return pon_dec(data, fs[0], fe[0]) } // inode
73 }
74 }
75 }
76 }
77 }
78 } }
79 ls = le + 1
80 }
81 return 0
82}
83// inode of a LISTEN socket on `port` across IPv4 + IPv6 (0 = nothing listening on it).
84func pon_listener_inode(port: i64) -> i64 {
85 let i4: i64 = pon_scan_file("/proc/net/tcp" as *u8, port)
86 if i4 > 0 { return i4 }
87 return pon_scan_file("/proc/net/tcp6" as *u8, port)
88}
89
90func pon_readlink(path: *u8, out: *u8, cap: i64) -> i64 {
91 let r: i64 = __syscall(PON_SYS_READLINKAT, PON_AT_FDCWD, path as i64, out as i64, cap, 0, 0)
92 if r < 0 { return 0 }
93 if r < cap { out[r] = 0 as u8 }
94 return r
95}
96func pon_str_all_digits(s: *u8) -> i64 { var i: i64 = 0; if s[0] == (0 as u8) { return 0 } while s[i] != (0 as u8) { let c: i64 = s[i] & 0xff; if c < 48 { return 0 } if c > 57 { return 0 } i = i + 1 } return 1 }
97func pon_join3(a: *u8, b: *u8, c: *u8, out: *u8) -> i64 {
98 var o: i64 = 0
99 var i: i64 = 0; while a[i] != (0 as u8) { out[o]=a[i]; o=o+1; i=i+1 }
100 i=0; while b[i] != (0 as u8) { out[o]=b[i]; o=o+1; i=i+1 }
101 i=0; while c[i] != (0 as u8) { out[o]=c[i]; o=o+1; i=i+1 }
102 out[o]=0 as u8; return o
103}
104func pon_join(a: *u8, b: *u8, c: *u8, d: *u8, out: *u8) -> i64 {
105 var o: i64 = 0
106 var i: i64 = 0; while a[i] != (0 as u8) { out[o]=a[i]; o=o+1; i=i+1 }
107 i=0; while b[i] != (0 as u8) { out[o]=b[i]; o=o+1; i=i+1 }
108 i=0; while c[i] != (0 as u8) { out[o]=c[i]; o=o+1; i=i+1 }
109 i=0; while d[i] != (0 as u8) { out[o]=d[i]; o=o+1; i=i+1 }
110 out[o]=0 as u8; return o
111}
112// does /proc/<piddir>/fd contain a symlink to socket:[inode]? returns 1/0.
113func pon_pid_has_inode(piddir: *u8, inode: i64) -> i64 {
114 let fdpath: *u8 = sys_mmap(256); pon_join3("/proc/" as *u8, piddir, "/fd" as *u8, fdpath)
115 let fd: i64 = sys_openat_rd(fdpath)
116 if fd < 0 { return 0 }
117 let buf: *u8 = sys_mmap(PON_MAGIC_32768); let lnk: *u8 = sys_mmap(256); let full: *u8 = sys_mmap(320)
118 var found: i64 = 0
119 var rn: i64 = sys_getdents64(fd, buf, PON_MAGIC_32768)
120 while rn > 0 {
121 var off: i64 = 0
122 while off < rn {
123 let rec: *u8 = (buf as i64 + off) as *u8
124 let nm: *u8 = dirent_name(rec)
125 if pon_str_all_digits(nm) == 1 {
126 pon_join("/proc/" as *u8, piddir, "/fd/" as *u8, nm, full)
127 let ln: i64 = pon_readlink(full, lnk, 256)
128 if ln > 8 {
129 if lnk[0]==(115 as u8) { if lnk[7]==(91 as u8) { // 's' ... '[' = socket:[
130 var k: i64 = 8; while k < ln { if lnk[k] == (93 as u8) { break } else { k = k+1 } }
131 if pon_dec(lnk, 8, k) == inode { found = 1 }
132 } }
133 }
134 }
135 let rl: i64 = dirent_reclen(rec); if rl <= 0 { off = rn } else { off = off + rl }
136 }
137 if found == 1 { rn = 0 } else { rn = sys_getdents64(fd, buf, PON_MAGIC_32768) }
138 }
139 sys_close(fd)
140 return found
141}
142// scan /proc/<pid> for the process owning `inode`; return the pid (0 = none).
143func pon_inode_to_pid(inode: i64) -> i64 {
144 let fd: i64 = sys_openat_rd("/proc" as *u8)
145 if fd < 0 { return 0 }
146 let buf: *u8 = sys_mmap(PON_MAGIC_32768)
147 var pid: i64 = 0
148 var rn: i64 = sys_getdents64(fd, buf, PON_MAGIC_32768)
149 while rn > 0 {
150 var off: i64 = 0
151 while off < rn {
152 let rec: *u8 = (buf as i64 + off) as *u8
153 let nm: *u8 = dirent_name(rec)
154 if pon_str_all_digits(nm) == 1 {
155 if pon_pid_has_inode(nm, inode) == 1 { pid = pon_dec(nm, 0, pon_strlen(nm)) }
156 }
157 let rl: i64 = dirent_reclen(rec); if rl <= 0 { off = rn } else { off = off + rl }
158 }
159 if pid > 0 { rn = 0 } else { rn = sys_getdents64(fd, buf, PON_MAGIC_32768) }
160 }
161 sys_close(fd)
162 return pid
163}
164// build "/proc/<pid>/<leaf>" into path
165func pon_pid_path(pid: i64, leaf: *u8, path: *u8) -> i64 {
166 let ps: *u8 = sys_mmap(24); var t: i64 = pid; var k: i64 = 0
167 let tmp: *u8 = sys_mmap(24)
168 if t == 0 { ps[0]=48 as u8; k=1 } else { while t > 0 { tmp[k] = (48 + (t%10)) as u8; t=t/10; k=k+1 } var j: i64 = 0; while j < k { ps[j] = tmp[k-1-j]; j=j+1 } }
169 ps[k] = 0 as u8
170 return pon_join3("/proc/" as *u8, ps, leaf, path)
171}
172// /proc/<pid>/comm -> out (the process name; kernel-truncated to 15 chars). returns length.
173func pon_pid_comm(pid: i64, out: *u8, cap: i64) -> i64 {
174 let path: *u8 = sys_mmap(64); pon_pid_path(pid, "/comm" as *u8, path)
175 let lb: *i64 = sys_mmap(8) as *i64
176 let d: *u8 = sys_read_file(path, lb)
177 if (d as i64) == 0 { out[0]=0 as u8; return 0 }
178 var n: i64 = lb[0]; if n > 0 { if d[n-1]==(10 as u8) { n = n-1 } }
179 var i: i64 = 0; while i < n { if i < cap-1 { out[i]=d[i] } i=i+1 } out[n]=0 as u8
180 return n
181}
182// /proc/<pid>/cmdline -> out (FULL command; NUL arg-separators rendered as spaces). The honest, un-truncated
183// identity (comm caps at 15 chars). returns length (0 = empty, e.g. a kernel thread -> caller falls back to comm).
184func pon_pid_cmdline(pid: i64, out: *u8, cap: i64) -> i64 {
185 let path: *u8 = sys_mmap(64); pon_pid_path(pid, "/cmdline" as *u8, path)
186 let lb: *i64 = sys_mmap(8) as *i64
187 let d: *u8 = sys_read_file(path, lb)
188 if (d as i64) == 0 { out[0]=0 as u8; return 0 }
189 var n: i64 = lb[0]
190 var i: i64 = 0
191 while i < n { if i < cap-1 { let c: i64 = d[i] & 0xff; if c == 0 { out[i] = 32 as u8 } else { out[i] = c as u8 } } i = i + 1 }
192 var w: i64 = n; if w > cap-1 { w = cap-1 }
193 while w > 0 { if out[w-1] == (32 as u8) { w = w - 1 } else { break } } // trim trailing space (the final NUL)
194 out[w] = 0 as u8
195 return w
196}
197// HIGH-LEVEL: which PID holds a LISTEN on `port`? 0 = nothing / free. Composes the chain in one call -- THE api
198// the supervisor snapshot + the mgmt plane + the twin call ("who really owns :8456?").
199func pon_port_owner_pid(port: i64) -> i64 {
200 let inode: i64 = pon_listener_inode(port)
201 if inode <= 0 { return 0 }
202 return pon_inode_to_pid(inode)
203}