cidr.nx source
↩ module page · 101 lines · 3511 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
18import "syscalls.nx"
19import "ipaddr.nx"
20
21const CIDR_ERR_FORMAT: i64 = -1
22const CIDR_ERR_BAD_PREFIX: i64 = -2
23
24// Parse "a.b.c.d/N" into ip (u32) + prefix. Writes to *ip_out,
25// *prefix_out. Returns 0 on success, negative CIDR_ERR_* on error.
26func cidr_parse(s: *u8, n: i64, ip_out: *i64, prefix_out: *i64) -> i64 {
27 // Find '/'.
28 var slash: i64 = 0 - 1
29 var i: i64 = 0
30 while i < n {
31 if s[i] == 0x2F { slash = i; i = n }
32 else { i = i + 1 }
33 }
34 if slash < 0 { return CIDR_ERR_FORMAT }
35
36 // Parse IP portion.
37 let ip_val: i64 = ipv4_parse(s, slash)
38 if ip_val < 0 { return ip_val }
39
40 // Parse prefix: decimal integer after '/'.
41 var prefix: i64 = 0
42 var j: i64 = slash + 1
43 var saw_digit: i64 = 0
44 while j < n {
45 let c: i64 = s[j]
46 if c < 0x30 { return CIDR_ERR_BAD_PREFIX }
47 if c > 0x39 { return CIDR_ERR_BAD_PREFIX }
48 prefix = prefix * 10 + (c - 0x30)
49 saw_digit = 1
50 j = j + 1
51 }
52 if saw_digit == 0 { return CIDR_ERR_BAD_PREFIX }
53 if prefix < 0 { return CIDR_ERR_BAD_PREFIX }
54 if prefix > 32 { return CIDR_ERR_BAD_PREFIX }
55
56 *ip_out = ip_val & 0xFFFFFFFF
57 *prefix_out = prefix
58 return 0
59}
60
61// Compute the bitmask for an IPv4 prefix length.
62// prefix=0 -> 0x00000000; prefix=24 -> 0xFFFFFF00; prefix=32 -> 0xFFFFFFFF.
63func cidr_mask(prefix: i64) -> i64 {
64 if prefix == 0 { return 0 }
65 if prefix >= 32 { return 0xFFFFFFFF }
66 let shift: i64 = 32 - prefix
67 let ones: i64 = (1 << 32) - 1
68 return (ones << shift) & 0xFFFFFFFF
69}
70
71// Canonicalise: clear host bits, return the network address.
72func cidr_network_address(ip: i64, prefix: i64) -> i64 {
73 return (ip & cidr_mask(prefix)) & 0xFFFFFFFF
74}
75
76// Does `addr` fall within the `network/prefix` CIDR block?
77func cidr_contains(network: i64, prefix: i64, addr: i64) -> i64 {
78 let m: i64 = cidr_mask(prefix)
79 if (network & m) == (addr & m) { return 1 }
80 return 0
81}
82
83// Compile-only smoke: parse "10.0.0.0/8", verify containment of
84// 10.1.2.3 and rejection of 192.168.1.1.
85func main() -> i64 {
86 let ip_slot: *i64 = sys_mmap(16) as *i64
87 let pfx_slot: *i64 = sys_mmap(16) as *i64
88 let rc: i64 = cidr_parse("10.0.0.0/8", 10, ip_slot, pfx_slot)
89 if rc != 0 { return 1 }
90 if *pfx_slot != 8 { return 2 }
91 if *ip_slot != 0x0A000000 { return 3 }
92
93 let m: i64 = cidr_mask(8)
94 if m != 0xFF000000 { return 4 }
95
96 // 10.1.2.3 = 0x0A010203 -- should match 10.0.0.0/8.
97 if cidr_contains(*ip_slot, *pfx_slot, 0x0A010203) != 1 { return 5 }
98 // 192.168.1.1 = 0xC0A80101 -- should NOT match.
99 if cidr_contains(*ip_slot, *pfx_slot, 0xC0A80101) != 0 { return 6 }
100 return 0
101}