code wiki / (root) / nx_os_introspect.nx

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}