code wiki / (root) / nx_ice.nx

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}