nx_cidr_smoke.nx source
↩ module page · 107 lines · 3667 B
1// cidr.nx -- IPv4 CIDR (RFC 4632) parser + match.
2//
3// Parses "a.b.c.d/N" notation and provides address-in-range
4// matching. Used by: ACLs, rate-limit rules, VPN route tables,
5// service discovery (DHCP scope definition).
6//
7// IPv6 CIDR (a:b:c::d/prefix) deferred -- same algorithm but
8// wider masks. Would share helpers with ipaddr.nx.
9//
10// Invariants:
11// CIDR1 Prefix length range-checked: [0, 32] for IPv4.
12// CIDR2 Host bits in the network address are IGNORED on
13// parse (RFC 4632 permissive). Canonical form masks
14// them out via cidr_network_address.
15// CIDR3 Match is bit-exact against the computed network +
16// mask; no regex, no partial parsing, no DNS resolution.
17
18// nx_safety_envelope:
19// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
20// sil_target: SIL1
21// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
22// verdict: NOT_YET_EVALUATED
23
24import "nx_syscalls.nx"
25import "nx_ipaddr.nx"
26
27const CIDR_ERR_FORMAT: i64 = -1
28const CIDR_ERR_BAD_PREFIX: i64 = -2
29
30// Parse "a.b.c.d/N" into ip (u32) + prefix. Writes to *ip_out,
31// *prefix_out. Returns 0 on success, negative CIDR_ERR_* on error.
32func cidr_parse(s: *u8, n: i64, ip_out: *i64, prefix_out: *i64) -> i64 {
33 // Find '/'.
34 var slash: i64 = 0 - 1
35 var i: i64 = 0
36 while i < n {
37 if s[i] == 0x2F { slash = i; i = n }
38 else { i = i + 1 }
39 }
40 if slash < 0 { return CIDR_ERR_FORMAT }
41
42 // Parse IP portion.
43 let ip_val: i64 = ipv4_parse(s, slash)
44 if ip_val < 0 { return ip_val }
45
46 // Parse prefix: decimal integer after '/'.
47 var prefix: i64 = 0
48 var j: i64 = slash + 1
49 var saw_digit: i64 = 0
50 while j < n {
51 let c: i64 = s[j]
52 if c < 0x30 { return CIDR_ERR_BAD_PREFIX }
53 if c > 0x39 { return CIDR_ERR_BAD_PREFIX }
54 prefix = prefix * 10 + (c - 0x30)
55 saw_digit = 1
56 j = j + 1
57 }
58 if saw_digit == 0 { return CIDR_ERR_BAD_PREFIX }
59 if prefix < 0 { return CIDR_ERR_BAD_PREFIX }
60 if prefix > 32 { return CIDR_ERR_BAD_PREFIX }
61
62 *ip_out = ip_val & 0xFFFFFFFF
63 *prefix_out = prefix
64 return 0
65}
66
67// Compute the bitmask for an IPv4 prefix length.
68// prefix=0 -> 0x00000000; prefix=24 -> 0xFFFFFF00; prefix=32 -> 0xFFFFFFFF.
69func cidr_mask(prefix: i64) -> i64 {
70 if prefix == 0 { return 0 }
71 if prefix >= 32 { return 0xFFFFFFFF }
72 let shift: i64 = 32 - prefix
73 let ones: i64 = (1 << 32) - 1
74 return (ones << shift) & 0xFFFFFFFF
75}
76
77// Canonicalise: clear host bits, return the network address.
78func cidr_network_address(ip: i64, prefix: i64) -> i64 {
79 return (ip & cidr_mask(prefix)) & 0xFFFFFFFF
80}
81
82// Does `addr` fall within the `network/prefix` CIDR block?
83func cidr_contains(network: i64, prefix: i64, addr: i64) -> i64 {
84 let m: i64 = cidr_mask(prefix)
85 if (network & m) == (addr & m) { return 1 }
86 return 0
87}
88
89// Compile-only smoke: parse "10.0.0.0/8", verify containment of
90// 10.1.2.3 and rejection of 192.168.1.1.
91func main() -> i64 {
92 let ip_slot: *i64 = sys_mmap(16) as *i64
93 let pfx_slot: *i64 = sys_mmap(16) as *i64
94 let rc: i64 = cidr_parse("10.0.0.0/8", 10, ip_slot, pfx_slot)
95 if rc != 0 { return 1 }
96 if *pfx_slot != 8 { return 2 }
97 if *ip_slot != 0x0A000000 { return 3 }
98
99 let m: i64 = cidr_mask(8)
100 if m != 0xFF000000 { return 4 }
101
102 // 10.1.2.3 = 0x0A010203 -- should match 10.0.0.0/8.
103 if cidr_contains(*ip_slot, *pfx_slot, 0x0A010203) != 1 { return 5 }
104 // 192.168.1.1 = 0xC0A80101 -- should NOT match.
105 if cidr_contains(*ip_slot, *pfx_slot, 0xC0A80101) != 0 { return 6 }
106 return 0
107}