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}