nx_ice.nx source
↩ module page · 166 lines · 8765 B
1// nx_ice.nx -- SOVEREIGN ICE (RFC 8445) primitives, NishiLang from the first byte up, composing nx_stun.
2//
3// Rung 2 of the Nishi P2P interop stack (rung 1 = nx_stun). ICE is how two peers behind NATs find a working
4// path to each other: each gathers candidate addresses (host / server-reflexive via STUN / relayed), ranks
5// them by PRIORITY, pairs them, and runs STUN Binding "connectivity checks" over each pair until one
6// succeeds. This module provides the ICE-specific pieces on top of the sovereign STUN codec, with ZERO
7// third-party code (no libnice, no libjuice), and KAT'd byte-exact against RFC 5769's own sample request.
8//
9// Provides:
10// - the ICE STUN attributes: PRIORITY, USE-CANDIDATE, ICE-CONTROLLED, ICE-CONTROLLING, USERNAME
11// - candidate PRIORITY (RFC 8445 5.1.2.1): 2^24*type_pref + 2^8*local_pref + (256 - component_id)
12// - candidate-PAIR priority as an overflow-safe COMPARATOR (the 2^32 form overflows i64; we only ever
13// need to order pairs, so we compare (min, max, controlling>controlled) lexicographically -- RFC 8445 6.1.2.3)
14// - a full connectivity-check Binding Request builder (SOFTWARE, PRIORITY, ICE-CONTROLLED, USERNAME,
15// MESSAGE-INTEGRITY, FINGERPRINT) that reproduces RFC 5769 2.1 byte-exact
16// license_tier: INDEPENDENT_REDERIVE genealogy_id: international-research-sources/ietf/rfc_8445 + rfc_5769
17import "nx_syscalls.nx"
18import "nx_stun.nx" // st_* header/xma/crc/fingerprint/message_integrity + STUN_* consts
19
20const ICE_ATTR_USERNAME: i64 = 0x0006
21const ICE_ATTR_PRIORITY: i64 = 0x0024
22const ICE_ATTR_USE_CANDIDATE: i64 = 0x0025
23const ICE_ATTR_ICE_CONTROLLED: i64 = 0x8029
24const ICE_ATTR_ICE_CONTROLLING: i64 = 0x802A
25const ICE_ATTR_SOFTWARE: i64 = 0x8022
26
27// RFC 8445 5.1.2.2 recommended type preferences
28const ICE_TYPEPREF_HOST: i64 = 126
29const ICE_TYPEPREF_PRFLX: i64 = 110
30const ICE_TYPEPREF_SRFLX: i64 = 100
31const ICE_TYPEPREF_RELAY: i64 = 0
32
33// RFC 8445 5.1.2.1 candidate priority. type_pref 0..126, local_pref 0..65535, component_id 1..256.
34func ic_priority(type_pref: i64, local_pref: i64, component_id: i64) -> i64 {
35 return (type_pref << 24) + (local_pref << 8) + (256 - component_id)
36}
37
38// generic TLV attribute writer: type(2) len(2) value pad-to-4. Returns the new offset.
39func ic_write_attr(buf: *u8, off: i64, atype: i64, val: *u8, vlen: i64, pad: i64) -> i64 {
40 st_put16(buf, off, atype)
41 st_put16(buf, off+2, vlen)
42 var i: i64 = 0
43 while i<vlen { buf[off+4+i]=val[i]; i=i+1 }
44 let padded: i64 = ((vlen+3)/4)*4
45 while i<padded { buf[off+4+i]=pad as u8; i=i+1 }
46 return off+4+padded
47}
48// PRIORITY attribute (fixed 4-byte value).
49func ic_write_priority(buf: *u8, off: i64, priority: i64) -> i64 {
50 st_put16(buf, off, ICE_ATTR_PRIORITY); st_put16(buf, off+2, 4); st_put32(buf, off+4, priority); return off+8
51}
52// ICE-CONTROLLED / ICE-CONTROLLING attribute (8-byte tie-breaker passed as raw bytes to dodge i64 sign issues).
53func ic_write_role(buf: *u8, off: i64, atype: i64, tb8: *u8) -> i64 {
54 st_put16(buf, off, atype); st_put16(buf, off+2, 8)
55 var i: i64 = 0
56 while i<8 { buf[off+4+i]=tb8[i]; i=i+1 }
57 return off+12
58}
59// USE-CANDIDATE (zero-length flag).
60func ic_write_use_candidate(buf: *u8, off: i64) -> i64 { st_put16(buf, off, ICE_ATTR_USE_CANDIDATE); st_put16(buf, off+2, 0); return off+4 }
61
62// ---- candidate-pair priority comparator (RFC 8445 6.1.2.3), overflow-safe ----
63func ic_imin(a: i64, b: i64) -> i64 { if a<b { return a } return b }
64func ic_imax(a: i64, b: i64) -> i64 { if a>b { return a } return b }
65// compare pair1(g1,d1) vs pair2(g2,d2): returns 1 if pair1 higher, -1 if lower, 0 equal.
66func ic_pair_cmp(g1: i64, d1: i64, g2: i64, d2: i64) -> i64 {
67 let m1: i64 = ic_imin(g1,d1); let x1: i64 = ic_imax(g1,d1); var b1: i64 = 0; if g1>d1 { b1=1 }
68 let m2: i64 = ic_imin(g2,d2); let x2: i64 = ic_imax(g2,d2); var b2: i64 = 0; if g2>d2 { b2=1 }
69 if m1!=m2 { if m1>m2 { return 1 } return 0-1 }
70 if x1!=x2 { if x1>x2 { return 1 } return 0-1 }
71 if b1!=b2 { if b1>b2 { return 1 } return 0-1 }
72 return 0
73}
74
75// ---- full connectivity-check Binding Request builder ----
76// Emits header + SOFTWARE + PRIORITY + ICE-CONTROLLED + USERNAME + MESSAGE-INTEGRITY + FINGERPRINT, computing
77// MI (over everything before it, length field set to +24) then FINGERPRINT (length field set to +8), per
78// RFC 5389 15.4/15.5. `pad` is the attribute padding byte (RFC 5769's vectors use 0x20; real senders may use
79// 0x00 -- receivers ignore padding). Returns total message length.
80func ic_build_check(buf: *u8, txid: *u8, sw: *u8, swlen: i64, priority: i64, tb8: *u8,
81 username: *u8, ulen: i64, password: *u8, plen: i64, pad: i64) -> i64 {
82 var o: i64 = st_write_header(buf, STUN_BINDING_REQUEST, 0, txid)
83 o = ic_write_attr(buf, o, ICE_ATTR_SOFTWARE, sw, swlen, pad)
84 o = ic_write_priority(buf, o, priority)
85 o = ic_write_role(buf, o, ICE_ATTR_ICE_CONTROLLED, tb8)
86 o = ic_write_attr(buf, o, ICE_ATTR_USERNAME, username, ulen, pad)
87 // MESSAGE-INTEGRITY: length field = (attrs so far) + 24; HMAC-SHA1 over bytes [0..o)
88 st_put16(buf, 2, (o-20)+24)
89 let mi: *u8 = sys_mmap(24)
90 st_message_integrity(password, plen, buf, o, mi)
91 st_put16(buf, o, STUN_ATTR_MESSAGE_INTEGRITY); st_put16(buf, o+2, 20)
92 var i: i64 = 0
93 while i<20 { buf[o+4+i]=mi[i]; i=i+1 }
94 o = o+24
95 // FINGERPRINT: length field = (attrs so far) + 8; CRC-32 over bytes [0..o)
96 st_put16(buf, 2, (o-20)+8)
97 let fp: i64 = st_fingerprint(buf, o)
98 st_put16(buf, o, STUN_ATTR_FINGERPRINT); st_put16(buf, o+2, 4); st_put32(buf, o+4, fp)
99 o = o+8
100 return o
101}
102
103// ---- connectivity-check RESPONDER side (validate an incoming check, answer it) ----
104
105// find an attribute by type; returns the byte offset of its VALUE, or -1. Walks TLV from offset 20.
106func ic_find_attr(msg: *u8, msglen: i64, atype: i64) -> i64 {
107 let mlen: i64 = st_len(msg)
108 var end: i64 = 20 + mlen
109 if end > msglen { end = msglen }
110 var off: i64 = 20
111 while off + 4 <= end {
112 let t: i64 = st_get16(msg, off)
113 let l: i64 = st_get16(msg, off+2)
114 if t == atype { return off + 4 }
115 off = off + 4 + ((l+3)/4)*4
116 }
117 return 0 - 1
118}
119func ic_has_use_candidate(msg: *u8, msglen: i64) -> i64 { if ic_find_attr(msg, msglen, ICE_ATTR_USE_CANDIDATE) >= 0 { return 1 } return 0 }
120
121// verify FINGERPRINT: the stored value must equal CRC-32(message up to the FINGERPRINT attribute) XOR magic.
122func ic_verify_fingerprint(msg: *u8, msglen: i64) -> i64 {
123 let fv: i64 = ic_find_attr(msg, msglen, STUN_ATTR_FINGERPRINT)
124 if fv < 0 { return 0 }
125 let attr_start: i64 = fv - 4
126 if st_fingerprint(msg, attr_start) != st_get32(msg, fv) { return 0 }
127 return 1
128}
129// verify MESSAGE-INTEGRITY under `key`: recompute HMAC-SHA1 over the message up to (excluding) the MI attr,
130// with the header length field temporarily set to (that length)-20+24, and compare to the stored 20 bytes.
131// Restores the length field so the check is side-effect-free. Returns 1 valid, 0 invalid/absent.
132func ic_verify_mi(msg: *u8, msglen: i64, key: *u8, keylen: i64) -> i64 {
133 let mv: i64 = ic_find_attr(msg, msglen, STUN_ATTR_MESSAGE_INTEGRITY)
134 if mv < 0 { return 0 }
135 let attr_start: i64 = mv - 4
136 let saved: i64 = st_len(msg)
137 st_put16(msg, 2, (attr_start-20)+24)
138 let calc: *u8 = sys_mmap(24)
139 st_message_integrity(key, keylen, msg, attr_start, calc)
140 st_put16(msg, 2, saved) // restore
141 var i: i64 = 0
142 var ok: i64 = 1
143 while i < 20 { if calc[i] != msg[mv+i] { ok = 0; i = 20 } else { i = i + 1 } }
144 return ok
145}
146// build a connectivity-check SUCCESS response: header(Binding Response) + XOR-MAPPED-ADDRESS(peer) +
147// MESSAGE-INTEGRITY(key) + FINGERPRINT. Mirrors RFC 8445 7.2.5.2.3 / RFC 5389 message assembly.
148func ic_build_success_response(buf: *u8, txid: *u8, peer_port: i64, peer_addr: i64, key: *u8, keylen: i64) -> i64 {
149 var o: i64 = st_write_header(buf, STUN_BINDING_RESPONSE, 0, txid)
150 st_put16(buf, o, STUN_ATTR_XOR_MAPPED_ADDRESS); st_put16(buf, o+2, 8)
151 let xp: *u8 = (buf as i64 + o + 4) as *u8
152 st_xma_encode(xp, peer_port, peer_addr)
153 o = o + 12
154 st_put16(buf, 2, (o-20)+24) // length for MESSAGE-INTEGRITY
155 let mi: *u8 = sys_mmap(24)
156 st_message_integrity(key, keylen, buf, o, mi)
157 st_put16(buf, o, STUN_ATTR_MESSAGE_INTEGRITY); st_put16(buf, o+2, 20)
158 var i: i64 = 0
159 while i < 20 { buf[o+4+i]=mi[i]; i=i+1 }
160 o = o + 24
161 st_put16(buf, 2, (o-20)+8) // length for FINGERPRINT
162 let fp: i64 = st_fingerprint(buf, o)
163 st_put16(buf, o, STUN_ATTR_FINGERPRINT); st_put16(buf, o+2, 4); st_put32(buf, o+4, fp)
164 o = o + 8
165 return o
166}