code wiki / (root) / nx_nft_read.nx

nx_nft_read.nx source

↩ module page · 139 lines · 8410 B

1// nx_nft_read.nx -- R0/R1 of the SOVEREIGN NETFILTER ("our own iptables", from the syscall rung up). READ the 2// kernel's "nat" table info via the LEGACY iptables getsockopt ABI (IPT_SO_GET_INFO) -- NO wheeler iptables binary. 3// Grounds the DNAT mechanism: proves sovereign netfilter-comms + reveals the table (num_entries/size) we'll append 4// a :443->:8444 DNAT to (R2), so DSM + Nishi COEXIST. READ-ONLY -- cannot disturb DSM's rules. The getsockopt 5// return value is itself the grounding: 0=read ok ; -1 EPERM=need root (legacy ABI present) ; -92 ENOPROTOOPT= 6// kernel is nft-only (switch to netlink) ; -2 ENOENT=no nat table. license_tier: ORIGINAL expect_exit: 0 7import "nx_syscalls.nx" 8 9func p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 10func pn(v: i64) -> i64 { let b: *u8=sys_mmap(28); var x: i64=v; if x<0 {b[0]=45;sys_write(1,b,1);x=0-x}; if x==0 {b[0]=48;sys_write(1,b,1);return 0} var d: i64=0; var y: i64=x; while y>0 {d=d+1;y=y/10} var i: i64=d-1; y=x; while i>=0 {b[i]=(48+(y%10)) as u8; y=y/10; i=i-1} sys_write(1,b,d); return 0 } 11func ru32(b: *u8, o: i64) -> i64 { return (b[o] as i64) | ((b[o+1] as i64)<<8) | ((b[o+2] as i64)<<16) | ((b[o+3] as i64)<<24) } 12 13 14const NR_ENTRY_BYTES: i64 = 112 15const NR_EXTENSION_BYTES: i64 = 32 16const NR_TARGET_OFFSET: i64 = 88 17const NR_NEXT_OFFSET: i64 = 90 18// Linux x86-64 UAPI layout: ip_tables.h / x_tables.h. These are ABI sizes, not resource limits. 19func nr_u16(b: *u8, o: i64) -> i64 { return (b[o] as i64) | ((b[o+1] as i64)<<8) } 20func nr_hex(b: *u8, n: i64) -> i64 { 21 if n==0 { return 0 } 22 let digits: *u8 = "0123456789abcdef" as *u8 23 let encoded: *u8 = sys_mmap(n*2) 24 var i: i64=0 25 while i<n { let v: i64=b[i] as i64; encoded[i*2]=digits[v>>4]; encoded[i*2+1]=digits[v&15]; i=i+1 } 26 var sent:i64=0 27 while sent<n*2 { let wrote:i64=sys_write(1,encoded+sent,n*2-sent); if wrote<=0 { sys_munmap(encoded,n*2); sys_exit(5); return 5 }; sent=sent+wrote } 28 sys_munmap(encoded,n*2); return 0 29} 30func nr_ip(b: *u8) -> i64 { 31 var i: i64=0 32 while i<4 { if i>0 { p("." as *u8) }; pn(b[i] as i64); i=i+1 } 33 return 0 34} 35func nr_name(b: *u8, n: i64) -> i64 { 36 var i: i64=0 37 while i<n { if b[i]==(0 as u8) { return 0 }; let v:i64=b[i] as i64 38 if v>=33 { if v<=126 { sys_write(1,b+i,1) } else { p("?" as *u8) } } else { p("?" as *u8) } 39 i=i+1 40 } 41 return 0 42} 43// Validate the complete entry walk before publishing any interpreted rules. 44func nr_validate(b: *u8, n: i64, expected: i64) -> i64 { 45 var off:i64=0; var count:i64=0 46 while off<n { 47 if n-off<NR_ENTRY_BYTES { return 1 } 48 let e:*u8=b+off; let next:i64=nr_u16(e,NR_NEXT_OFFSET); let target:i64=nr_u16(e,NR_TARGET_OFFSET) 49 if next<NR_ENTRY_BYTES+NR_EXTENSION_BYTES { return 2 } 50 if next>n-off { return 3 } 51 if (next&7)!=0 { return 4 } 52 if target<NR_ENTRY_BYTES { return 5 } 53 if target>next-NR_EXTENSION_BYTES { return 6 } 54 let size:i64=nr_u16(e,target) 55 if size<NR_EXTENSION_BYTES { return 7 } 56 if size!=next-target { return 8 } 57 var m:i64=NR_ENTRY_BYTES 58 while m<target { 59 if target-m<NR_EXTENSION_BYTES { return 9 } 60 let ms:i64=nr_u16(e,m) 61 if ms<NR_EXTENSION_BYTES { return 10 } 62 if ms>target-m { return 11 } 63 if (ms&7)!=0 { return 12 } 64 m=m+ms 65 } 66 off=off+next; count=count+1 67 } 68 if count!=expected { return 13 } 69 return 0 70} 71func nr_emit(b: *u8, n: i64) -> i64 { 72 var off:i64=0; var count:i64=0 73 while off<n { 74 let e:*u8=b+off; let next:i64=nr_u16(e,NR_NEXT_OFFSET); let target:i64=nr_u16(e,NR_TARGET_OFFSET) 75 p("RULE offset=" as *u8); pn(off); p(" src=" as *u8); nr_ip(e); p("/" as *u8); nr_ip(e+8) 76 p(" dst=" as *u8); nr_ip(e+4); p("/" as *u8); nr_ip(e+12) 77 p(" in=" as *u8); nr_name(e+16,16); p(" out=" as *u8); nr_name(e+32,16) 78 p(" proto=" as *u8); pn(nr_u16(e,80)); p(" flags=" as *u8); pn(e[82] as i64) 79 p(" inverse=" as *u8); pn(e[83] as i64) 80 p(" target=" as *u8); nr_name(e+target+2,29); p(" revision=" as *u8); pn(e[target+31] as i64) 81 p(" target_data_hex=" as *u8); nr_hex(e+target+NR_EXTENSION_BYTES,next-target-NR_EXTENSION_BYTES) 82 p("\n" as *u8) 83 var m:i64=NR_ENTRY_BYTES 84 while m<target { 85 let ms:i64=nr_u16(e,m) 86 p(" MATCH name=" as *u8); nr_name(e+m+2,29) 87 p(" revision=" as *u8); pn(e[m+31] as i64) 88 p(" data_hex=" as *u8); nr_hex(e+m+NR_EXTENSION_BYTES,ms-NR_EXTENSION_BYTES); p("\n" as *u8) 89 m=m+ms 90 } 91 p(" ENTRY_RAW offset=" as *u8); pn(off); p(" hex=" as *u8); nr_hex(e,next); p("\n" as *u8) 92 off=off+next; count=count+1 93 } 94 p("NAT_INSPECTION entries=" as *u8); pn(count); p(" bytes=" as *u8); pn(n) 95 p(" capture_complete=1 structure_valid=1 mutation=none interpretation=ipv4-legacy-rule-fields-and-lossless-raw\n" as *u8) 96 return 0 97} 98 99func main() -> i64 { 100 // The kernel UAPI permits an ordinary IPv4 socket; table access is checked by getsockopt. 101 let fd: i64 = sys_socket(2, 1, 0) 102 if fd < 0 { p("SOCKET rc=" as *u8); pn(fd); p(" (IPv4 socket creation failed; table inspection not attempted)\n" as *u8); sys_exit(2); return 2 } 103 // struct ipt_getinfo { char name[32]; u32 valid_hooks; u32 hook_entry[5]; u32 underflow[5]; u32 num_entries; u32 size; } = 84 bytes 104 let info: *u8 = sys_mmap(128) 105 info[0]=110 as u8; info[1]=97 as u8; info[2]=116 as u8; info[3]=0 as u8 // "nat\0" (mmap zeroes the rest) 106 let lb: *i64 = (sys_mmap(8)) as *i64; lb[0] = 84 107 // getsockopt(fd, SOL_IP=0, IPT_SO_GET_INFO=64, info, &len) -- x86_64 syscall 55 108 let rc: i64 = __syscall(55, fd, 0, 64, info as i64, lb as i64) 109 if rc < 0 { 110 p("GET_INFO rc=" as *u8); pn(rc); p(" [read failed; inspect execution capabilities and kernel table support; errno alone does not establish nft-only]\n" as *u8) 111 sys_close(fd); sys_exit(3); return 3 112 } 113 p("nat table READ ok -- valid_hooks=" as *u8); pn(ru32(info, 32)) 114 p(" num_entries=" as *u8); pn(ru32(info, 76)) 115 p(" size=" as *u8); pn(ru32(info, 80)); p(" bytes (legacy iptables ABI confirmed)\n" as *u8) 116 // hook_entry[5]@36 / underflow[5]@56 (byte offsets into the entry blob). nat hooks = 0 PRE,1 IN,3 OUT,4 POST 117 // (hook 2 FORWARD invalid for nat). underflow[0] = the PREROUTING policy offset = where R2 inserts the DNAT. 118 p(" PRE/IN/OUT/POST chain-start=" as *u8) 119 pn(ru32(info,36)); p("," as *u8); pn(ru32(info,40)); p("," as *u8); pn(ru32(info,48)); p("," as *u8); pn(ru32(info,52)) 120 p(" policy(underflow)=" as *u8) 121 pn(ru32(info,56)); p("," as *u8); pn(ru32(info,60)); p("," as *u8); pn(ru32(info,68)); p("," as *u8); pn(ru32(info,72)); p("\n" as *u8) 122 // GET_ENTRIES: read the WHOLE table so R2 can splice + SET_REPLACE without losing DSM's rules. 123 // struct ipt_get_entries { char name[32]; u32 size; struct ipt_entry entrytable[]; } -- entrytable filled by kernel. 124 let tsz: i64 = ru32(info, 80) 125 let geb: *u8 = sys_mmap(tsz + 64) 126 geb[0]=110 as u8; geb[1]=97 as u8; geb[2]=116 as u8; geb[3]=0 as u8 // "nat\0" 127 geb[32]=(tsz & 255) as u8; geb[33]=((tsz>>8)&255) as u8; geb[34]=((tsz>>16)&255) as u8; geb[35]=((tsz>>24)&255) as u8 128 // sizeof(struct ipt_get_entries) = 40 (name[32] + u32 size@32 + 4 pad@36 -> entrytable[] 8-byte aligned@40). 129 // The kernel requires *optlen == sizeof(ipt_get_entries) + get.size == 40 + tsz. (36 -> EINVAL/-22.) 130 let lb2: *i64 = (sys_mmap(8)) as *i64; lb2[0] = tsz + 40 131 let rc2: i64 = __syscall(55, fd, 0, 65, geb as i64, lb2 as i64) // getsockopt IPT_SO_GET_ENTRIES=65 132 if rc2 < 0 { p("NAT_INSPECTION stage=get_entries syscall_result=" as *u8); pn(rc2); p(" capture_complete=0 action=resolve-kernel-read-failure-before-route-diagnosis\n" as *u8); sys_close(fd); sys_exit(3); return 3 } 133 sys_close(fd) 134 if lb2[0]!=tsz+40 { p("NAT_INSPECTION stage=length capture_complete=0 action=recapture-consistent-table\n" as *u8); sys_exit(4); return 4 } 135 let valid:i64=nr_validate(geb+40,tsz,ru32(info,76)) 136 if valid!=0 { p("NAT_INSPECTION stage=entry-validation cause=" as *u8); pn(valid); p(" capture_complete=0 action=inspect-ABI-or-concurrent-table-change\n" as *u8); sys_exit(4); return 4 } 137 nr_emit(geb+40,tsz) 138 sys_exit(0); return 0 139}