nx_os_introspect.nx source
↩ module page · 548 lines · 28400 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}
204
205
206// Additive traversal observation, not process identity or kill authority.
207// COMPLETE means every entry encountered in this non-atomic traversal was readable.
208// PID reuse and processes changing between reads remain outside this contract.
209const PON_OBS_COMPLETE: i64 = 0
210const PON_OBS_UNKNOWN: i64 = 1
211const PON_OBS_CAPACITY: i64 = 2
212const PON_OBS_BAD_INPUT: i64 = 3
213const PON_OBS_STATE_SLOTS: i64 = 6
214const PON_OBS_STATUS: i64 = 0
215const PON_OBS_FIRST_ERROR: i64 = 1
216const PON_OBS_STORED: i64 = 2
217const PON_OBS_PIDS: i64 = 3
218const PON_OBS_ERRORS: i64 = 4
219const PON_OBS_MATCHES: i64 = 5
220const PON_OBS_ROW_SLOTS: i64 = 3
221const PON_OBS_PID: i64 = 0
222const PON_OBS_FD: i64 = 1
223const PON_OBS_INODE: i64 = 2
224const PON_OBS_I64_MAX: i64 = 9223372036854775807
225const PON_OBS_DIRENT_NAME: i64 = 19
226const PON_OBS_E_FORMAT: i64 = 0-74
227const PON_OBS_E_ALLOC: i64 = 0-12
228const PON_OBS_E_SATURATED: i64 = 0-75
229const PON_OBS_I64_BYTES: i64 = 8
230const PON_OBS_I64_TEXT: i64 = 20
231
232func pon_obs_init(st: *i64) -> i64 {
233 var i: i64=0; while i < PON_OBS_STATE_SLOTS { st[i]=0; i=i+1 }; return 0
234}
235func pon_obs_error(st: *i64, error: i64) -> i64 {
236 if st[PON_OBS_STATUS] == PON_OBS_COMPLETE { st[PON_OBS_STATUS]=PON_OBS_UNKNOWN }
237 if st[PON_OBS_ERRORS] == 0 { st[PON_OBS_FIRST_ERROR]=error }
238 st[PON_OBS_ERRORS]=st[PON_OBS_ERRORS]+1
239 return st[PON_OBS_STATUS]
240}
241func pon_obs_uint(b: *u8, a: i64, e: i64) -> i64 {
242 if a < 0 || a >= e || e-a > PON_OBS_I64_TEXT { return 0-1 }
243 var v: i64=0; var p: i64=a
244 while p < e {
245 let d: i64=(b[p] as i64)-48
246 if d < 0 || d > 9 { return 0-1 }
247 if v > (PON_OBS_I64_MAX-d)/10 { return 0-1 }
248 v=v*10+d; p=p+1
249 }; return v
250}
251// A negative value is malformed socket syntax; zero means a different symlink kind.
252func pon_obs_socket_inode(b: *u8, n: i64) -> i64 {
253 let prefix: *u8="socket:[" as *u8
254 let pn: i64=pon_strlen(prefix)
255 if n < pn { return 0 }
256 var p: i64=0
257 while p < pn { if b[p] != prefix[p] { return 0 }; p=p+1 }
258 if n <= pn || b[n-1] != (93 as u8) { return 0-1 }
259 let inode: i64=pon_obs_uint(b,pn,n-1)
260 if inode <= 0 { return 0-1 }; return inode
261}
262// Returns reclen after validating both the record boundary and NUL-terminated name.
263func pon_obs_dirent(b: *u8, off: i64, n: i64) -> i64 {
264 if n-off <= PON_OBS_DIRENT_NAME { return 0-1 }
265 let rec: *u8=((b as i64)+off) as *u8
266 let size: i64=dirent_reclen(rec)
267 if size <= PON_OBS_DIRENT_NAME || size > n-off { return 0-1 }
268 var p: i64=PON_OBS_DIRENT_NAME
269 while p < size { if rec[p] == (0 as u8) { return size }; p=p+1 }
270 return 0-1
271}
272func pon_obs_number(out: *u8, at: i64, n: i64) -> i64 {
273 let first: i64=at; var end: i64=at; var value: i64=n
274 if value == 0 { out[end]=48 as u8; end=end+1 }
275 while value > 0 { out[end]=(48+value%10) as u8; end=end+1; value=value/10 }
276 var a: i64=first; var z: i64=end-1
277 while a < z { let c: u8=out[a]; out[a]=out[z]; out[z]=c; a=a+1; z=z-1 }
278 out[end]=0 as u8; return end
279}
280// Accumulate every matching FD for this PID. Caller initialized state and owns row capacity.
281func pon_observe_pid_inode(pid: i64, inode: i64, rows: *i64, capacity: i64, st: *i64) -> i64 {
282 if (st as i64) <= 0 { return PON_OBS_BAD_INPUT }
283 if pid <= 0 || inode <= 0 || capacity < 0 { st[PON_OBS_STATUS]=PON_OBS_BAD_INPUT; return PON_OBS_BAD_INPUT }
284 if capacity > PON_OBS_I64_MAX/(PON_OBS_ROW_SLOTS*PON_OBS_I64_BYTES) { st[PON_OBS_STATUS]=PON_OBS_BAD_INPUT; return PON_OBS_BAD_INPUT }
285 if capacity > 0 && (rows as i64) <= 0 { st[PON_OBS_STATUS]=PON_OBS_BAD_INPUT; return PON_OBS_BAD_INPUT }
286 let name: *u8=sys_mmap(SYS_PATH_MAX)
287 let path: *u8=sys_mmap(SYS_PATH_MAX)
288 let full: *u8=sys_mmap(SYS_PATH_MAX)
289 let links: *u8=sys_mmap(SYS_PATH_MAX)
290 let buf: *u8=sys_mmap(PON_MAGIC_32768)
291 if (name as i64) <= 0 || (path as i64) <= 0 || (full as i64) <= 0 || (links as i64) <= 0 || (buf as i64) <= 0 {
292 if (name as i64) > 0 { sys_munmap(name,SYS_PATH_MAX) }
293 if (path as i64) > 0 { sys_munmap(path,SYS_PATH_MAX) }
294 if (full as i64) > 0 { sys_munmap(full,SYS_PATH_MAX) }
295 if (links as i64) > 0 { sys_munmap(links,SYS_PATH_MAX) }
296 if (buf as i64) > 0 { sys_munmap(buf,PON_MAGIC_32768) }
297 return pon_obs_error(st,PON_OBS_E_ALLOC)
298 }
299 pon_obs_number(name,0,pid)
300 pon_join3("/proc/" as *u8,name,"/fd" as *u8,path)
301 let fd: i64=sys_openat_rd(path)
302 st[PON_OBS_PIDS]=st[PON_OBS_PIDS]+1
303 if fd < 0 { pon_obs_error(st,fd) } else {
304 var run: i64=1
305 while run == 1 {
306 let n: i64=sys_getdents64(fd,buf,PON_MAGIC_32768)
307 if n < 0 { pon_obs_error(st,n); run=0 } else { if n == 0 { run=0 } else {
308 var off: i64=0
309 while off < n {
310 let step: i64=pon_obs_dirent(buf,off,n)
311 if step < 0 { pon_obs_error(st,PON_OBS_E_FORMAT); off=n } else {
312 let entry: *u8=dirent_name(((buf as i64)+off) as *u8)
313 let entry_n: i64=pon_strlen(entry)
314 let number: i64=pon_obs_uint(entry,0,entry_n)
315 if number < 0 { if pon_str_all_digits(entry) == 1 { pon_obs_error(st,PON_OBS_E_FORMAT) } }
316 if number >= 0 {
317 pon_join("/proc/" as *u8,name,"/fd/" as *u8,entry,full)
318 let ln: i64=sys_readlinkat(full,links,SYS_PATH_MAX)
319 if ln < 0 { pon_obs_error(st,ln) } else { if ln >= SYS_PATH_MAX { pon_obs_error(st,PON_OBS_E_SATURATED) } else {
320 let observed: i64=pon_obs_socket_inode(links,ln)
321 if observed < 0 { pon_obs_error(st,PON_OBS_E_FORMAT) }
322 if observed == inode {
323 st[PON_OBS_MATCHES]=st[PON_OBS_MATCHES]+1
324 let stored: i64=st[PON_OBS_STORED]
325 if stored >= capacity { st[PON_OBS_STATUS]=PON_OBS_CAPACITY } else {
326 let row: i64=stored*PON_OBS_ROW_SLOTS
327 rows[row+PON_OBS_PID]=pid; rows[row+PON_OBS_FD]=number; rows[row+PON_OBS_INODE]=inode
328 st[PON_OBS_STORED]=stored+1
329 }
330 }
331 } }
332 }
333 off=off+step
334 }
335 }
336 } }
337 }
338 let closed: i64=sys_close(fd); if closed < 0 { pon_obs_error(st,closed) }
339 }
340 sys_munmap(name,SYS_PATH_MAX); sys_munmap(path,SYS_PATH_MAX); sys_munmap(full,SYS_PATH_MAX)
341 sys_munmap(links,SYS_PATH_MAX); sys_munmap(buf,PON_MAGIC_32768)
342 return st[PON_OBS_STATUS]
343}
344// All /proc PID entries encountered, all matching FDs retained separately.
345// No start-time/stable PID claim; no atomic cross-process snapshot claim.
346func pon_observe_inode(inode: i64, rows: *i64, capacity: i64, st: *i64) -> i64 {
347 if (st as i64) <= 0 { return PON_OBS_BAD_INPUT }
348 pon_obs_init(st)
349 if inode <= 0 || capacity < 0 || capacity > PON_OBS_I64_MAX/(PON_OBS_ROW_SLOTS*PON_OBS_I64_BYTES) { st[PON_OBS_STATUS]=PON_OBS_BAD_INPUT; return PON_OBS_BAD_INPUT }
350 if capacity > 0 && (rows as i64) <= 0 { st[PON_OBS_STATUS]=PON_OBS_BAD_INPUT; return PON_OBS_BAD_INPUT }
351 let fd: i64=sys_openat_rd("/proc" as *u8)
352 if fd < 0 { return pon_obs_error(st,fd) }
353 let buf: *u8=sys_mmap(PON_MAGIC_32768)
354 if (buf as i64) <= 0 { sys_close(fd); return pon_obs_error(st,PON_OBS_E_ALLOC) }
355 var run: i64=1
356 while run == 1 {
357 let n: i64=sys_getdents64(fd,buf,PON_MAGIC_32768)
358 if n < 0 { pon_obs_error(st,n); run=0 } else { if n == 0 { run=0 } else {
359 var off: i64=0
360 while off < n {
361 let step: i64=pon_obs_dirent(buf,off,n)
362 if step < 0 { pon_obs_error(st,PON_OBS_E_FORMAT); off=n } else {
363 let entry: *u8=dirent_name(((buf as i64)+off) as *u8)
364 let pid: i64=pon_obs_uint(entry,0,pon_strlen(entry))
365 if pid < 0 { if pon_str_all_digits(entry) == 1 { pon_obs_error(st,PON_OBS_E_FORMAT) } }
366 if pid > 0 { pon_observe_pid_inode(pid,inode,rows,capacity,st) }
367 off=off+step
368 }
369 }
370 } }
371 }
372 let closed: i64=sys_close(fd); if closed < 0 { pon_obs_error(st,closed) }
373 sys_munmap(buf,PON_MAGIC_32768)
374 return st[PON_OBS_STATUS]
375}
376
377// Complete traversal of matching TCP listeners, preserving /proc address text.
378// Family is 4 or 6; address text is kernel hex notation, not normalized IP text.
379// Separate IPv4/IPv6 reads are not an atomic network snapshot or stop authority.
380const PON_L_STATE_SLOTS: i64 = 9
381const PON_L_STATUS: i64 = 0
382const PON_L_FIRST_ERROR: i64 = 1
383const PON_L_STORED: i64 = 2
384const PON_L_MATCHES: i64 = 3
385const PON_L_ROWS: i64 = 4
386const PON_L_ERRORS: i64 = 5
387const PON_L_FILES_COMPLETE: i64 = 6
388const PON_L_BYTES: i64 = 7
389const PON_L_FIRST_STAGE: i64 = 8
390const PON_L_STAGE_INPUT: i64 = 1
391const PON_L_STAGE_OPEN: i64 = 2
392const PON_L_STAGE_READ: i64 = 3
393const PON_L_STAGE_PARSE: i64 = 4
394const PON_L_STAGE_ROWS: i64 = 5
395const PON_L_STAGE_CLOSE: i64 = 6
396const PON_L_STAGE_ALLOC: i64 = 7
397const PON_L_ROW_SLOTS: i64 = 4
398const PON_L_FAMILY: i64 = 0
399const PON_L_PORT: i64 = 1
400const PON_L_INODE: i64 = 2
401const PON_L_ADDRESS_N: i64 = 3
402const PON_L_ADDRESS_STRIDE: i64 = 33
403const PON_L_V4_HEX: i64 = 8
404const PON_L_V6_HEX: i64 = 32
405const PON_L_PORT_HEX: i64 = 4
406const PON_L_STATE_HEX: i64 = 2
407const PON_L_PORT_MAX: i64 = 65535
408const PON_L_F_LOCAL: i64 = 1
409const PON_L_F_STATE: i64 = 3
410const PON_L_F_INODE: i64 = 9
411const PON_L_FAMILY4: i64 = 4
412const PON_L_FAMILY6: i64 = 6
413const PON_L_HEADER_FIELDS: i64 = 12
414
415func pon_listener_init(st: *i64) -> i64 {
416 var i: i64=0; while i < PON_L_STATE_SLOTS { st[i]=0; i=i+1 }; return 0
417}
418func pon_listener_error(st: *i64, stage: i64, error: i64, capacity: i64) -> i64 {
419 if st[PON_L_STATUS] == PON_OBS_COMPLETE { st[PON_L_STATUS]=PON_OBS_UNKNOWN }
420 if capacity == 1 { st[PON_L_STATUS]=PON_OBS_CAPACITY }
421 if st[PON_L_ERRORS] == 0 { st[PON_L_FIRST_ERROR]=error; st[PON_L_FIRST_STAGE]=stage }
422 st[PON_L_ERRORS]=st[PON_L_ERRORS]+1; return st[PON_L_STATUS]
423}
424func pon_listener_hex_digit(c: i64) -> i64 {
425 if c >= 48 && c <= 57 { return c-48 }
426 if c >= 65 && c <= 70 { return c-55 }
427 if c >= 97 && c <= 102 { return c-87 }
428 return 0-1
429}
430func pon_listener_hex(b: *u8, a: i64, e: i64) -> i64 {
431 if a < 0 || e <= a { return 0-1 }
432 var n: i64=0; var p: i64=a
433 while p < e {
434 let d: i64=pon_listener_hex_digit(b[p] as i64)
435 if d < 0 || n > (PON_OBS_I64_MAX-d)/16 { return 0-1 }
436 n=n*16+d; p=p+1
437 }; return n
438}
439func pon_listener_args(port: i64, rows: *i64, addresses: *u8, capacity: i64) -> i64 {
440 if port <= 0 || port > PON_L_PORT_MAX || capacity < 0 { return 0 }
441 if capacity > PON_OBS_I64_MAX/PON_L_ADDRESS_STRIDE { return 0 }
442 if capacity > PON_OBS_I64_MAX/(PON_L_ROW_SLOTS*PON_OBS_I64_BYTES) { return 0 }
443 if capacity > 0 { if (rows as i64) <= 0 || (addresses as i64) <= 0 { return 0 } }
444 return 1
445}
446// Verified kernel headers have twelve whitespace-separated names. Queue/timer
447// columns combine in data rows, so header indices must not replace data indices.
448func pon_listener_header(buf: *u8, a: i64, e: i64, family: i64, fields: *i64) -> i64 {
449 var expected: *u8="sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode" as *u8
450 if family == PON_L_FAMILY6 { expected="sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode" as *u8 }
451 let expected_n: i64=pon_strlen(expected)
452 var field: i64=0; var expected_a: i64=0; var expected_e: i64=0
453 let fs: *i64=fields; let fe: *i64=((fields as i64)+PON_OBS_I64_BYTES) as *i64
454 while field < PON_L_HEADER_FIELDS {
455 if pon_field(buf,a,e,field,fs,fe) != 1 { return 0 }
456 if pon_field(expected,0,expected_n,field,&expected_a,&expected_e) != 1 { return 0 }
457 if fe[0]-fs[0] != expected_e-expected_a { return 0 }
458 var p: i64=0
459 while p < expected_e-expected_a { if buf[fs[0]+p] != expected[expected_a+p] { return 0 }; p=p+1 }
460 field=field+1
461 }
462 if pon_field(buf,a,e,PON_L_HEADER_FIELDS,fs,fe) == 1 { return 0 }
463 return 1
464}
465// A single data row; malformed rows never supply a plausible partial observation.
466func pon_listener_row(buf: *u8, a: i64, e: i64, family: i64, port: i64, rows: *i64, addresses: *u8, capacity: i64, st: *i64, fields: *i64) -> i64 {
467 let fs: *i64=fields; let fe: *i64=((fields as i64)+PON_OBS_I64_BYTES) as *i64
468 st[PON_L_ROWS]=st[PON_L_ROWS]+1
469 if pon_field(buf,a,e,PON_L_F_STATE,fs,fe) != 1 { return pon_listener_error(st,PON_L_STAGE_PARSE,PON_OBS_E_FORMAT,0) }
470 if fe[0]-fs[0] != PON_L_STATE_HEX { return pon_listener_error(st,PON_L_STAGE_PARSE,PON_OBS_E_FORMAT,0) }
471 let state: i64=pon_listener_hex(buf,fs[0],fe[0])
472 if state < 0 { return pon_listener_error(st,PON_L_STAGE_PARSE,PON_OBS_E_FORMAT,0) }
473 if pon_field(buf,a,e,PON_L_F_LOCAL,fs,fe) != 1 { return pon_listener_error(st,PON_L_STAGE_PARSE,PON_OBS_E_FORMAT,0) }
474 let address_a: i64=fs[0]; var address_n: i64=PON_L_V4_HEX
475 if family == PON_L_FAMILY6 { address_n=PON_L_V6_HEX }
476 if fe[0]-address_a != address_n+1+PON_L_PORT_HEX { return pon_listener_error(st,PON_L_STAGE_PARSE,PON_OBS_E_FORMAT,0) }
477 if buf[address_a+address_n] != (58 as u8) { return pon_listener_error(st,PON_L_STAGE_PARSE,PON_OBS_E_FORMAT,0) }
478 var p: i64=0
479 while p < address_n {
480 if pon_listener_hex_digit(buf[address_a+p] as i64) < 0 { return pon_listener_error(st,PON_L_STAGE_PARSE,PON_OBS_E_FORMAT,0) }; p=p+1
481 }
482 let observed_port: i64=pon_listener_hex(buf,address_a+address_n+1,fe[0])
483 if observed_port < 0 { return pon_listener_error(st,PON_L_STAGE_PARSE,PON_OBS_E_FORMAT,0) }
484 if state != PON_TCP_LISTEN || observed_port != port { return st[PON_L_STATUS] }
485 if pon_field(buf,a,e,PON_L_F_INODE,fs,fe) != 1 { return pon_listener_error(st,PON_L_STAGE_PARSE,PON_OBS_E_FORMAT,0) }
486 let inode: i64=pon_obs_uint(buf,fs[0],fe[0])
487 if inode <= 0 { return pon_listener_error(st,PON_L_STAGE_PARSE,PON_OBS_E_FORMAT,0) }
488 st[PON_L_MATCHES]=st[PON_L_MATCHES]+1
489 let index: i64=st[PON_L_STORED]
490 if index >= capacity { return pon_listener_error(st,PON_L_STAGE_ROWS,PON_OBS_E_SATURATED,1) }
491 let r: i64=index*PON_L_ROW_SLOTS
492 rows[r+PON_L_FAMILY]=family; rows[r+PON_L_PORT]=port; rows[r+PON_L_INODE]=inode; rows[r+PON_L_ADDRESS_N]=address_n
493 p=0
494 while p < address_n { addresses[index*PON_L_ADDRESS_STRIDE+p]=buf[address_a+p]; p=p+1 }
495 addresses[index*PON_L_ADDRESS_STRIDE+address_n]=0 as u8
496 st[PON_L_STORED]=index+1; return st[PON_L_STATUS]
497}
498// Accumulates one COMPLETE source buffer. Caller initializes state before the first source.
499func pon_listener_parse(buf: *u8, n: i64, family: i64, port: i64, rows: *i64, addresses: *u8, capacity: i64, st: *i64) -> i64 {
500 if (st as i64) <= 0 { return PON_OBS_BAD_INPUT }
501 if n <= 0 || (buf as i64) <= 0 || (family != PON_L_FAMILY4 && family != PON_L_FAMILY6) || pon_listener_args(port,rows,addresses,capacity) == 0 { st[PON_L_STATUS]=PON_OBS_BAD_INPUT; return PON_OBS_BAD_INPUT }
502 let fields: *i64=sys_mmap(2*PON_OBS_I64_BYTES) as *i64
503 if (fields as i64) <= 0 { return pon_listener_error(st,PON_L_STAGE_ALLOC,PON_OBS_E_ALLOC,0) }
504 var p: i64=0; var first: i64=1
505 while p < n {
506 var e: i64=p
507 while e < n { if buf[e] == (10 as u8) { break }; e=e+1 }
508 if first == 1 {
509 if pon_listener_header(buf,p,e,family,fields) != 1 { pon_listener_error(st,PON_L_STAGE_PARSE,PON_OBS_E_FORMAT,0) }
510 first=0
511 } else { if e > p { pon_listener_row(buf,p,e,family,port,rows,addresses,capacity,st,fields) } }
512 p=e+1
513 }
514 sys_munmap(fields as *u8,2*PON_OBS_I64_BYTES); return st[PON_L_STATUS]
515}
516// Caller-specified byte capacity, with an extra-byte probe; never parse a truncated prefix.
517func pon_listener_file(path: *u8, family: i64, port: i64, rows: *i64, addresses: *u8, capacity: i64, read_capacity: i64, st: *i64) -> i64 {
518 if (st as i64) <= 0 { return PON_OBS_BAD_INPUT }
519 if (path as i64) <= 0 || read_capacity <= 0 || read_capacity >= PON_OBS_I64_MAX/2 || (family != PON_L_FAMILY4 && family != PON_L_FAMILY6) || pon_listener_args(port,rows,addresses,capacity) == 0 { st[PON_L_STATUS]=PON_OBS_BAD_INPUT; return PON_OBS_BAD_INPUT }
520 let fd: i64=sys_openat_rd(path)
521 if fd < 0 { return pon_listener_error(st,PON_L_STAGE_OPEN,fd,0) }
522 let buf: *u8=sys_mmap(read_capacity+1)
523 if (buf as i64) <= 0 { sys_close(fd); return pon_listener_error(st,PON_L_STAGE_ALLOC,PON_OBS_E_ALLOC,0) }
524 var total: i64=0; var complete: i64=0; var run: i64=1
525 while run == 1 {
526 let got: i64=sys_read(fd,((buf as i64)+total) as *u8,read_capacity+1-total)
527 if got < 0 { pon_listener_error(st,PON_L_STAGE_READ,got,0); run=0 } else { if got == 0 { complete=1; run=0 } else {
528 total=total+got
529 if total > read_capacity { pon_listener_error(st,PON_L_STAGE_READ,PON_OBS_E_SATURATED,1); run=0 }
530 } }
531 }
532 let closed: i64=sys_close(fd)
533 if closed < 0 { pon_listener_error(st,PON_L_STAGE_CLOSE,closed,0); complete=0 }
534 st[PON_L_BYTES]=st[PON_L_BYTES]+total
535 if complete == 1 {
536 st[PON_L_FILES_COMPLETE]=st[PON_L_FILES_COMPLETE]+1
537 if total <= 0 { pon_listener_error(st,PON_L_STAGE_PARSE,PON_OBS_E_FORMAT,0) } else { pon_listener_parse(buf,total,family,port,rows,addresses,capacity,st) }
538 }
539 sys_munmap(buf,read_capacity+1); return st[PON_L_STATUS]
540}
541func pon_observe_listeners(port: i64, rows: *i64, addresses: *u8, capacity: i64, read_capacity: i64, st: *i64) -> i64 {
542 if (st as i64) <= 0 { return PON_OBS_BAD_INPUT }
543 pon_listener_init(st)
544 if read_capacity <= 0 || read_capacity >= PON_OBS_I64_MAX/2 || pon_listener_args(port,rows,addresses,capacity) == 0 { st[PON_L_STATUS]=PON_OBS_BAD_INPUT; return PON_OBS_BAD_INPUT }
545 pon_listener_file("/proc/net/tcp" as *u8,PON_L_FAMILY4,port,rows,addresses,capacity,read_capacity,st)
546 pon_listener_file("/proc/net/tcp6" as *u8,PON_L_FAMILY6,port,rows,addresses,capacity,read_capacity,st)
547 return st[PON_L_STATUS]
548}