code wiki / (root) / nx_turn_relay.nx

nx_turn_relay.nx source

↩ module page · 883 lines · 38219 B

1// nx_turn_relay.nx -- Sovereign TURN relay foundation (RFC 5766/8656). 2// Arc 4 Phase A.1 per [[NISHI_BITS_UP_EXCEED_INDUSTRY]]. 3// 4// Closes the openrelay.metered.ca dependency that's currently 5// `slow-lane-grandfathered` in web_assets/video.js. Once nx_turn_relay 6// ships on Texas Synology + Hetzner Frankfurt VPS, the slow-lane 7// audit grader can flag any remaining openrelay reference as a real 8// violation per [[feedback-no-slow-lane-no-paid-priority]]. 9// 10// This file provides the message FRAMING layer: 11// - nx_turn_parse_allocate: parse a TURN ALLOCATE request 12// - nx_turn_build_allocate_success: build a 0x0103 success response 13// - nx_turn_build_allocate_error: build a 0x0113 error response 14// 15// Future phases (per [[NISHI_S_CLASS_ROUTING_ROADMAP]] Q1): 16// - Phase A.2: SEND indication forwarding (relay → peer) 17// - Phase A.3: DATA indication (peer → relay → client) 18// - Phase A.4: CREATE-PERMISSION (allow specific peer IPs) 19// - Phase A.5: REFRESH (extend allocation lifetime) 20// - Phase A.6: CHANNEL-BIND (compact data channel) 21// - Phase B: short-term credentials (HMAC-SHA1 MESSAGE-INTEGRITY) 22// - Phase C: multi-egress + QUIC-on-443 fingerprint (slow-lane cardinal) 23// 24// Patent-clean math: TURN is an IETF standard, royalty-free per 25// IETF IPR policy. We implement the wire format; no proprietary 26// extensions. 27 28// nx_turn_relay.nx -- RFC 5766 message framing layer only (NO HMAC, 29// NO syscalls). Pure byte-manipulation primitives. Importable by 30// either syscall-clan consumer (x86_64 daemon via nx_udp's 31// nx_syscalls_x86_64; or WASM smoke via nx_hmac_sha1's nx_syscalls) 32// without a duplicate-symbol clash. See 33// [[reference-syscall-module-duality-nx_syscalls-vs-syscalls]]. 34// MESSAGE-INTEGRITY in nx_turn_msgintegrity.nx. 35 36// ============================================================================ 37// TURN message types (RFC 5766 §13) 38// ============================================================================ 39const NX_TURN_TYPE_ALLOCATE_REQUEST: i64 = 0x0003 40const NX_TURN_TYPE_ALLOCATE_SUCCESS: i64 = 0x0103 41const NX_TURN_TYPE_ALLOCATE_ERROR: i64 = 0x0113 42const NX_TURN_TYPE_REFRESH_REQUEST: i64 = 0x0004 43const NX_TURN_TYPE_SEND_INDICATION: i64 = 0x0016 44const NX_TURN_TYPE_DATA_INDICATION: i64 = 0x0017 45const NX_TURN_TYPE_CREATE_PERM_REQUEST: i64 = 0x0008 46const NX_TURN_TYPE_CHANNEL_BIND_REQUEST: i64 = 0x0009 47 48// STUN attributes used by TURN (RFC 5766 §14) 49const NX_TURN_ATTR_MAPPED_ADDR: i64 = 0x0001 50const NX_TURN_ATTR_USERNAME: i64 = 0x0006 51const NX_TURN_ATTR_MESSAGE_INTEGRITY: i64 = 0x0008 52const NX_TURN_ATTR_ERROR_CODE: i64 = 0x0009 53const NX_TURN_ATTR_REALM: i64 = 0x0014 54const NX_TURN_ATTR_NONCE: i64 = 0x0015 55const NX_TURN_ATTR_XOR_RELAYED_ADDR: i64 = 0x0016 56const NX_TURN_ATTR_REQUESTED_TRANSPORT: i64 = 0x0019 57const NX_TURN_ATTR_DONT_FRAGMENT: i64 = 0x001A 58const NX_TURN_ATTR_LIFETIME: i64 = 0x000D 59const NX_TURN_ATTR_XOR_MAPPED_ADDR: i64 = 0x0020 60const NX_TURN_ATTR_SOFTWARE: i64 = 0x8022 61 62// STUN magic cookie (RFC 5389 §6) 63const NX_STUN_MAGIC: i64 = 0x2112A442 64 65// Transport protocols (RFC 5766 §14.7) 66const NX_TURN_TRANSPORT_UDP: i64 = 17 67const NX_TURN_TRANSPORT_TCP: i64 = 6 68 69// Address families (RFC 5389 §15.1) 70const NX_TURN_FAMILY_IPV4: i64 = 0x01 71const NX_TURN_FAMILY_IPV6: i64 = 0x02 72 73// Verdicts for parse_allocate 74const NX_TURN_VERDICT_OK_ALLOCATE: i64 = 1 75const NX_TURN_VERDICT_BAD_MAGIC: i64 = 2 76const NX_TURN_VERDICT_TOO_SHORT: i64 = 3 77const NX_TURN_VERDICT_UNKNOWN_TYPE: i64 = 4 78const NX_TURN_VERDICT_MISSING_TRANSPORT: i64 = 5 79const NX_TURN_VERDICT_UNSUPP_TRANSPORT: i64 = 6 80 81// ============================================================================ 82// Byte helpers 83// ============================================================================ 84func _turn_read_be16(buf: *u8, off: i64) -> i64 { 85 let hi: i64 = buf[off] 86 let lo: i64 = buf[off + 1] 87 return (hi << 8) | lo 88} 89 90func _turn_write_be16(buf: *u8, off: i64, v: i64) -> i64 { 91 buf[off] = (v >> 8) & 0xff 92 buf[off + 1] = v & 0xff 93 return 2 94} 95 96func _turn_write_be32(buf: *u8, off: i64, v: i64) -> i64 { 97 buf[off] = (v >> 24) & 0xff 98 buf[off + 1] = (v >> 16) & 0xff 99 buf[off + 2] = (v >> 8) & 0xff 100 buf[off + 3] = v & 0xff 101 return 4 102} 103 104// ============================================================================ 105// Parse ALLOCATE request 106// ============================================================================ 107// 108// STUN/TURN message format (RFC 5389 §6): 109// 0 1 2 3 110// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 111// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 112// |0 0| STUN Message Type | Message Length | 113// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 114// | Magic Cookie | 115// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 116// | | 117// | Transaction ID (96 bits) | 118// | | 119// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 120// | Attributes (TLV) | 121// ~ ~ 122// 123// Each attribute is 4-byte aligned: type(2) + length(2) + value(length, padded). 124// 125// On success, writes: 126// - tx_id_out[0..12]: 12-byte transaction ID 127// - requested_transport_out[0]: transport protocol (17=UDP) 128// Returns NX_TURN_VERDICT_OK_ALLOCATE. 129 130func nx_turn_parse_allocate(buf: *u8, n: i64, 131 tx_id_out: *u8, 132 requested_transport_out: *u8) -> i64 { 133 if n < 20 { return NX_TURN_VERDICT_TOO_SHORT } 134 // First 2 bits must be 0 (STUN message) 135 if (buf[0] & 0xc0) != 0 { return NX_TURN_VERDICT_UNKNOWN_TYPE } 136 let msg_type: i64 = _turn_read_be16(buf, 0) 137 if msg_type != NX_TURN_TYPE_ALLOCATE_REQUEST { return NX_TURN_VERDICT_UNKNOWN_TYPE } 138 let msg_len: i64 = _turn_read_be16(buf, 2) 139 if n < 20 + msg_len { return NX_TURN_VERDICT_TOO_SHORT } 140 // Check magic cookie 141 if buf[4] != 0x21 { return NX_TURN_VERDICT_BAD_MAGIC } 142 if buf[5] != 0x12 { return NX_TURN_VERDICT_BAD_MAGIC } 143 if buf[6] != 0xa4 { return NX_TURN_VERDICT_BAD_MAGIC } 144 if buf[7] != 0x42 { return NX_TURN_VERDICT_BAD_MAGIC } 145 // Copy transaction ID (12 bytes at offset 8) 146 var i: i64 = 0 147 while i < 12 { 148 tx_id_out[i] = buf[8 + i] 149 i = i + 1 150 } 151 // Parse attributes 152 var pos: i64 = 20 153 let end: i64 = 20 + msg_len 154 var saw_transport: i64 = 0 155 var transport: i64 = 0 156 while pos + 4 <= end { 157 let attr_type: i64 = _turn_read_be16(buf, pos) 158 let attr_len: i64 = _turn_read_be16(buf, pos + 2) 159 let val_off: i64 = pos + 4 160 if val_off + attr_len > end { return NX_TURN_VERDICT_TOO_SHORT } 161 if attr_type == NX_TURN_ATTR_REQUESTED_TRANSPORT { 162 // 4-byte value: 1 byte protocol + 3 bytes RFFU(=0) 163 if attr_len == 4 { 164 transport = buf[val_off] 165 saw_transport = 1 166 } 167 } 168 // Advance to next attribute (4-byte aligned) 169 var padded: i64 = attr_len 170 let rem: i64 = attr_len & 3 171 if rem != 0 { padded = attr_len + (4 - rem) } 172 pos = val_off + padded 173 } 174 if saw_transport == 0 { return NX_TURN_VERDICT_MISSING_TRANSPORT } 175 if transport != NX_TURN_TRANSPORT_UDP { return NX_TURN_VERDICT_UNSUPP_TRANSPORT } 176 requested_transport_out[0] = transport 177 return NX_TURN_VERDICT_OK_ALLOCATE 178} 179 180// ============================================================================ 181// Build XOR-MAPPED-ADDRESS attribute (RFC 5389 §15.2, also XOR-RELAYED-ADDRESS 182// for TURN RFC 5766 §14.5). IPv4 only for now. 183// 184// Value format (8 bytes): 185// byte 0: 0 186// byte 1: family (0x01 = IPv4) 187// byte 2-3: X-Port = port XOR (magic>>16) (big-endian) 188// byte 4-7: X-Address = addr XOR magic (big-endian) 189// 190// Returns total bytes written (4 header + 8 value = 12). 191// ============================================================================ 192func _turn_write_xor_address(buf: *u8, off: i64, attr_type: i64, 193 addr_be: *u8, port: i64) -> i64 { 194 _turn_write_be16(buf, off, attr_type) 195 _turn_write_be16(buf, off + 2, 8) 196 let val: i64 = off + 4 197 buf[val] = 0 198 buf[val + 1] = NX_TURN_FAMILY_IPV4 199 // port XOR top-16 bits of magic 200 let xor_port: i64 = port ^ ((NX_STUN_MAGIC >> 16) & 0xffff) 201 _turn_write_be16(buf, val + 2, xor_port) 202 // addr XOR magic (4 bytes) 203 buf[val + 4] = addr_be[0] ^ ((NX_STUN_MAGIC >> 24) & 0xff) 204 buf[val + 5] = addr_be[1] ^ ((NX_STUN_MAGIC >> 16) & 0xff) 205 buf[val + 6] = addr_be[2] ^ ((NX_STUN_MAGIC >> 8) & 0xff) 206 buf[val + 7] = addr_be[3] ^ ( NX_STUN_MAGIC & 0xff) 207 return 12 208} 209 210// Build LIFETIME attribute (4-byte value = lifetime in seconds). 211// Returns total bytes written (4 header + 4 value = 8). 212func _turn_write_lifetime(buf: *u8, off: i64, lifetime_sec: i64) -> i64 { 213 _turn_write_be16(buf, off, NX_TURN_ATTR_LIFETIME) 214 _turn_write_be16(buf, off + 2, 4) 215 _turn_write_be32(buf, off + 4, lifetime_sec) 216 return 8 217} 218 219// ============================================================================ 220// Build ALLOCATE success response (0x0103) 221// 222// Includes: 223// - XOR-RELAYED-ADDRESS (the relayed transport address the server allocated) 224// - LIFETIME (how long the allocation lasts in seconds) 225// - XOR-MAPPED-ADDRESS (the client's reflexive transport address) 226// 227// Returns total bytes written. 228// ============================================================================ 229func nx_turn_build_allocate_success(out_buf: *u8, tx_id: *u8, 230 relayed_addr: *u8, relayed_port: i64, 231 mapped_addr: *u8, mapped_port: i64, 232 lifetime_sec: i64) -> i64 { 233 // Header: type + length + magic + tx_id 234 _turn_write_be16(out_buf, 0, NX_TURN_TYPE_ALLOCATE_SUCCESS) 235 // length filled in later; placeholder 0 236 _turn_write_be16(out_buf, 2, 0) 237 out_buf[4] = 0x21; out_buf[5] = 0x12; out_buf[6] = 0xa4; out_buf[7] = 0x42 238 var i: i64 = 0 239 while i < 12 { out_buf[8 + i] = tx_id[i]; i = i + 1 } 240 var pos: i64 = 20 241 // XOR-RELAYED-ADDRESS (12 bytes) 242 pos = pos + _turn_write_xor_address(out_buf, pos, NX_TURN_ATTR_XOR_RELAYED_ADDR, 243 relayed_addr, relayed_port) 244 // LIFETIME (8 bytes) 245 pos = pos + _turn_write_lifetime(out_buf, pos, lifetime_sec) 246 // XOR-MAPPED-ADDRESS (12 bytes) 247 pos = pos + _turn_write_xor_address(out_buf, pos, NX_TURN_ATTR_XOR_MAPPED_ADDR, 248 mapped_addr, mapped_port) 249 // Fill in message length (total - 20 header bytes) 250 let attr_len: i64 = pos - 20 251 _turn_write_be16(out_buf, 2, attr_len) 252 return pos 253} 254 255// ============================================================================ 256// Build ALLOCATE error response (0x0113) 257// 258// Includes ERROR-CODE attribute (RFC 5389 §15.6): 259// byte 0-1: reserved (=0) 260// byte 2: class (1-6, encoded as Class * 100 = full error number) 261// byte 3: number (full_code mod 100) 262// bytes 4..: UTF-8 reason phrase 263// 264// Common TURN error codes: 265// 400 = Bad Request 266// 401 = Unauthorized (short-term creds path) 267// 441 = Wrong Credentials 268// 442 = Unsupported Transport Protocol 269// 270// Returns total bytes written. 271// ============================================================================ 272func nx_turn_build_allocate_error(out_buf: *u8, tx_id: *u8, 273 error_code: i64, 274 reason: *u8, reason_len: i64) -> i64 { 275 _turn_write_be16(out_buf, 0, NX_TURN_TYPE_ALLOCATE_ERROR) 276 _turn_write_be16(out_buf, 2, 0) 277 out_buf[4] = 0x21; out_buf[5] = 0x12; out_buf[6] = 0xa4; out_buf[7] = 0x42 278 var i: i64 = 0 279 while i < 12 { out_buf[8 + i] = tx_id[i]; i = i + 1 } 280 // ERROR-CODE attribute 281 let value_len: i64 = 4 + reason_len 282 _turn_write_be16(out_buf, 20, NX_TURN_ATTR_ERROR_CODE) 283 _turn_write_be16(out_buf, 22, value_len) 284 out_buf[24] = 0 285 out_buf[25] = 0 286 out_buf[26] = error_code / 100 287 out_buf[27] = error_code - (error_code / 100) * 100 288 var j: i64 = 0 289 while j < reason_len { 290 out_buf[28 + j] = reason[j] 291 j = j + 1 292 } 293 // Pad to 4-byte alignment 294 var pos: i64 = 28 + reason_len 295 let rem: i64 = reason_len & 3 296 if rem != 0 { 297 let pad: i64 = 4 - rem 298 var p: i64 = 0 299 while p < pad { out_buf[pos + p] = 0; p = p + 1 } 300 pos = pos + pad 301 } 302 let attr_len: i64 = pos - 20 303 _turn_write_be16(out_buf, 2, attr_len) 304 return pos 305} 306 307// ============================================================================ 308// Smoke export: build → parse → check round-trip 309// Returns 0 on success, non-zero bitmap on failure. 310// bit 0: build succeeded but parse rejected 311// bit 1: parse OK but tx_id mismatched 312// bit 2: parse OK but requested-transport not UDP 313// ============================================================================ 314func nx_turn_allocate_roundtrip_test(scratch: *u8) -> i64 { 315 let buf: *u8 = scratch 316 let tx_in: *u8 = (scratch as i64 + 2048) as *u8 317 let tx_out: *u8 = (scratch as i64 + 2080) as *u8 318 let req_xport: *u8 = (scratch as i64 + 2112) as *u8 319 // Build a synthetic ALLOCATE request (tx_id = 0x01, 0x02, ..., 0x0c) 320 var ti: i64 = 0 321 while ti < 12 { tx_in[ti] = ti + 1; ti = ti + 1 } 322 // Header 323 _turn_write_be16(buf, 0, NX_TURN_TYPE_ALLOCATE_REQUEST) 324 _turn_write_be16(buf, 2, 8) // attributes total length 325 buf[4] = 0x21; buf[5] = 0x12; buf[6] = 0xa4; buf[7] = 0x42 326 var bi: i64 = 0 327 while bi < 12 { buf[8 + bi] = tx_in[bi]; bi = bi + 1 } 328 // REQUESTED-TRANSPORT attribute: type(2) + len=4 + value(4) 329 _turn_write_be16(buf, 20, NX_TURN_ATTR_REQUESTED_TRANSPORT) 330 _turn_write_be16(buf, 22, 4) 331 buf[24] = NX_TURN_TRANSPORT_UDP 332 buf[25] = 0; buf[26] = 0; buf[27] = 0 // RFFU 333 // Parse it back 334 let verdict: i64 = nx_turn_parse_allocate(buf, 28, tx_out, req_xport) 335 var fail: i64 = 0 336 if verdict != NX_TURN_VERDICT_OK_ALLOCATE { fail = fail | 1 } 337 var tj: i64 = 0 338 while tj < 12 { 339 if tx_in[tj] != tx_out[tj] { fail = fail | 2 } 340 tj = tj + 1 341 } 342 if req_xport[0] != NX_TURN_TRANSPORT_UDP { fail = fail | 4 } 343 return fail 344} 345 346// ============================================================================ 347// Build XOR-PEER-ADDRESS attribute (RFC 5766 §14.3). 348// Identical wire format to XOR-RELAYED/MAPPED-ADDRESS; only attr_type differs. 349const NX_TURN_ATTR_XOR_PEER_ADDR: i64 = 0x0012 350const NX_TURN_ATTR_DATA: i64 = 0x0013 351 352// Build DATA attribute (RFC 5766 §14.4). 353// Value is the raw payload bytes (no encoding); padded to 4-byte alignment. 354// Returns total bytes written (4 header + payload + padding). 355func _turn_write_data_attr(buf: *u8, off: i64, 356 payload: *u8, payload_len: i64) -> i64 { 357 _turn_write_be16(buf, off, NX_TURN_ATTR_DATA) 358 _turn_write_be16(buf, off + 2, payload_len) 359 var i: i64 = 0 360 while i < payload_len { 361 buf[off + 4 + i] = payload[i] 362 i = i + 1 363 } 364 // Pad to 4-byte alignment with zero bytes 365 var written: i64 = 4 + payload_len 366 let rem: i64 = payload_len & 3 367 if rem != 0 { 368 let pad: i64 = 4 - rem 369 var p: i64 = 0 370 while p < pad { 371 buf[off + 4 + payload_len + p] = 0 372 p = p + 1 373 } 374 written = written + pad 375 } 376 return written 377} 378 379// ============================================================================ 380// Build SEND indication (client → server, type 0x0016 with 0x01 in top bit 381// position making it an indication: actually 0x0016 is correct for SEND). 382// 383// RFC 5766 §10: SEND is an Indication (no response). Contains: 384// - XOR-PEER-ADDRESS: which peer the payload is for 385// - DATA: payload bytes 386// 387// Returns total bytes written. 388// ============================================================================ 389func nx_turn_build_send_indication(out_buf: *u8, tx_id: *u8, 390 peer_addr: *u8, peer_port: i64, 391 payload: *u8, payload_len: i64) -> i64 { 392 _turn_write_be16(out_buf, 0, NX_TURN_TYPE_SEND_INDICATION) 393 _turn_write_be16(out_buf, 2, 0) 394 out_buf[4] = 0x21; out_buf[5] = 0x12; out_buf[6] = 0xa4; out_buf[7] = 0x42 395 var i: i64 = 0 396 while i < 12 { out_buf[8 + i] = tx_id[i]; i = i + 1 } 397 var pos: i64 = 20 398 pos = pos + _turn_write_xor_address(out_buf, pos, NX_TURN_ATTR_XOR_PEER_ADDR, 399 peer_addr, peer_port) 400 pos = pos + _turn_write_data_attr(out_buf, pos, payload, payload_len) 401 let attr_len: i64 = pos - 20 402 _turn_write_be16(out_buf, 2, attr_len) 403 return pos 404} 405 406// ============================================================================ 407// Build DATA indication (server → client, type 0x0017). 408// 409// Identical structure to SEND, just different type code. 410// Used when a peer sends data to the relay; the relay wraps it in DATA 411// and forwards to the client. 412// ============================================================================ 413func nx_turn_build_data_indication(out_buf: *u8, tx_id: *u8, 414 peer_addr: *u8, peer_port: i64, 415 payload: *u8, payload_len: i64) -> i64 { 416 _turn_write_be16(out_buf, 0, NX_TURN_TYPE_DATA_INDICATION) 417 _turn_write_be16(out_buf, 2, 0) 418 out_buf[4] = 0x21; out_buf[5] = 0x12; out_buf[6] = 0xa4; out_buf[7] = 0x42 419 var i: i64 = 0 420 while i < 12 { out_buf[8 + i] = tx_id[i]; i = i + 1 } 421 var pos: i64 = 20 422 pos = pos + _turn_write_xor_address(out_buf, pos, NX_TURN_ATTR_XOR_PEER_ADDR, 423 peer_addr, peer_port) 424 pos = pos + _turn_write_data_attr(out_buf, pos, payload, payload_len) 425 let attr_len: i64 = pos - 20 426 _turn_write_be16(out_buf, 2, attr_len) 427 return pos 428} 429 430// ============================================================================ 431// Parse SEND or DATA indication. Extracts peer address + port + payload. 432// 433// expected_type: NX_TURN_TYPE_SEND_INDICATION or NX_TURN_TYPE_DATA_INDICATION 434// Writes: 435// peer_addr_out[0..3]: IPv4 address (de-XOR'd) 436// peer_port_out: i64 LE (8 bytes) — port (de-XOR'd, low 16 bits) 437// payload_out: copied payload bytes 438// payload_len_out: i64 LE (8 bytes) — payload length 439// 440// Returns 0 on success, non-zero error code. 441// ============================================================================ 442func nx_turn_parse_indication(buf: *u8, n: i64, 443 expected_type: i64, 444 peer_addr_out: *u8, 445 peer_port_out: *u8, 446 payload_out: *u8, 447 payload_len_out: *u8) -> i64 { 448 if n < 20 { return 1 } 449 if _turn_read_be16(buf, 0) != expected_type { return 2 } 450 let msg_len: i64 = _turn_read_be16(buf, 2) 451 if n < 20 + msg_len { return 3 } 452 if buf[4] != 0x21 { return 4 } 453 if buf[5] != 0x12 { return 4 } 454 if buf[6] != 0xa4 { return 4 } 455 if buf[7] != 0x42 { return 4 } 456 var pos: i64 = 20 457 let end: i64 = 20 + msg_len 458 var saw_peer: i64 = 0 459 var saw_data: i64 = 0 460 while pos + 4 <= end { 461 let attr_type: i64 = _turn_read_be16(buf, pos) 462 let attr_len: i64 = _turn_read_be16(buf, pos + 2) 463 let val_off: i64 = pos + 4 464 if val_off + attr_len > end { return 5 } 465 if attr_type == NX_TURN_ATTR_XOR_PEER_ADDR { 466 if attr_len == 8 { 467 // family check 468 if buf[val_off + 1] == NX_TURN_FAMILY_IPV4 { 469 // de-XOR port (low 16 bits of magic) 470 let xor_port_hi: i64 = buf[val_off + 2] 471 let xor_port_lo: i64 = buf[val_off + 3] 472 let xor_port: i64 = (xor_port_hi << 8) | xor_port_lo 473 let port: i64 = xor_port ^ ((NX_STUN_MAGIC >> 16) & 0xffff) 474 peer_port_out[0] = port & 0xff 475 peer_port_out[1] = (port >> 8) & 0xff 476 peer_port_out[2] = 0 477 peer_port_out[3] = 0 478 peer_port_out[4] = 0 479 peer_port_out[5] = 0 480 peer_port_out[6] = 0 481 peer_port_out[7] = 0 482 // de-XOR address (full 32 bits of magic) 483 peer_addr_out[0] = buf[val_off + 4] ^ ((NX_STUN_MAGIC >> 24) & 0xff) 484 peer_addr_out[1] = buf[val_off + 5] ^ ((NX_STUN_MAGIC >> 16) & 0xff) 485 peer_addr_out[2] = buf[val_off + 6] ^ ((NX_STUN_MAGIC >> 8) & 0xff) 486 peer_addr_out[3] = buf[val_off + 7] ^ ( NX_STUN_MAGIC & 0xff) 487 saw_peer = 1 488 } 489 } 490 } 491 if attr_type == NX_TURN_ATTR_DATA { 492 var pi: i64 = 0 493 while pi < attr_len { 494 payload_out[pi] = buf[val_off + pi] 495 pi = pi + 1 496 } 497 payload_len_out[0] = attr_len & 0xff 498 payload_len_out[1] = (attr_len >> 8) & 0xff 499 payload_len_out[2] = (attr_len >> 16) & 0xff 500 payload_len_out[3] = (attr_len >> 24) & 0xff 501 payload_len_out[4] = 0 502 payload_len_out[5] = 0 503 payload_len_out[6] = 0 504 payload_len_out[7] = 0 505 saw_data = 1 506 } 507 var padded: i64 = attr_len 508 let rem: i64 = attr_len & 3 509 if rem != 0 { padded = attr_len + (4 - rem) } 510 pos = val_off + padded 511 } 512 if saw_peer == 0 { return 6 } 513 if saw_data == 0 { return 7 } 514 return 0 515} 516 517// Smoke: build SEND, parse it back, check peer + payload match. 518// Then build DATA from same peer/payload, parse, check. 519// Returns 0 on full success, non-zero bitmap on failure. 520func nx_turn_send_data_roundtrip_test(scratch: *u8) -> i64 { 521 let buf: *u8 = scratch 522 let tx_id: *u8 = (scratch as i64 + 2048) as *u8 523 let peer_addr_in: *u8 = (scratch as i64 + 2080) as *u8 524 let payload_in: *u8 = (scratch as i64 + 2096) as *u8 525 let peer_addr_out: *u8 = (scratch as i64 + 2160) as *u8 526 let peer_port_out: *u8 = (scratch as i64 + 2176) as *u8 527 let payload_out: *u8 = (scratch as i64 + 2192) as *u8 528 let payload_len_out: *u8 = (scratch as i64 + 2256) as *u8 529 530 // Setup: tx_id = 0xA0..0xAB, peer = 10.20.30.40:55555, payload = 32 bytes 531 var i: i64 = 0 532 while i < 12 { tx_id[i] = 0xa0 + i; i = i + 1 } 533 peer_addr_in[0] = 10; peer_addr_in[1] = 20; peer_addr_in[2] = 30; peer_addr_in[3] = 40 534 var pi: i64 = 0 535 while pi < 32 { payload_in[pi] = pi * 7 + 3; pi = pi + 1 } 536 537 var fail: i64 = 0 538 539 // SEND round-trip 540 let send_len: i64 = nx_turn_build_send_indication(buf, tx_id, 541 peer_addr_in, 55555, payload_in, 32) 542 let send_rc: i64 = nx_turn_parse_indication(buf, send_len, 543 NX_TURN_TYPE_SEND_INDICATION, 544 peer_addr_out, peer_port_out, payload_out, payload_len_out) 545 if send_rc != 0 { fail = fail | 1 } 546 if peer_addr_out[0] != 10 { fail = fail | 2 } 547 if peer_addr_out[1] != 20 { fail = fail | 2 } 548 if peer_addr_out[2] != 30 { fail = fail | 2 } 549 if peer_addr_out[3] != 40 { fail = fail | 2 } 550 let port_lo: i64 = peer_port_out[0] 551 let port_hi: i64 = peer_port_out[1] 552 let port_out: i64 = port_lo | (port_hi << 8) 553 if port_out != 55555 { fail = fail | 4 } 554 let plen: i64 = payload_len_out[0] | (payload_len_out[1] << 8) 555 if plen != 32 { fail = fail | 8 } 556 var pj: i64 = 0 557 while pj < 32 { 558 if payload_in[pj] != payload_out[pj] { fail = fail | 16 } 559 pj = pj + 1 560 } 561 562 // DATA round-trip 563 let data_len: i64 = nx_turn_build_data_indication(buf, tx_id, 564 peer_addr_in, 55555, payload_in, 32) 565 let data_rc: i64 = nx_turn_parse_indication(buf, data_len, 566 NX_TURN_TYPE_DATA_INDICATION, 567 peer_addr_out, peer_port_out, payload_out, payload_len_out) 568 if data_rc != 0 { fail = fail | 32 } 569 if peer_addr_out[0] != 10 { fail = fail | 64 } 570 var pk: i64 = 0 571 while pk < 32 { 572 if payload_in[pk] != payload_out[pk] { fail = fail | 128 } 573 pk = pk + 1 574 } 575 return fail 576} 577 578// ============================================================================ 579// Arc 4 Phase A.3 -- CREATE-PERMISSION + REFRESH + CHANNEL-BIND 580// ============================================================================ 581 582const NX_TURN_TYPE_REFRESH_SUCCESS: i64 = 0x0104 583const NX_TURN_TYPE_REFRESH_ERROR: i64 = 0x0114 584const NX_TURN_TYPE_CREATE_PERM_SUCCESS: i64 = 0x0108 585const NX_TURN_TYPE_CREATE_PERM_ERROR: i64 = 0x0118 586const NX_TURN_TYPE_CHANNEL_BIND_SUCCESS: i64 = 0x0109 587const NX_TURN_TYPE_CHANNEL_BIND_ERROR: i64 = 0x0119 588const NX_TURN_ATTR_CHANNEL_NUMBER: i64 = 0x000C 589 590// REFRESH (RFC 5766 §7): client extends or destroys its allocation. 591// Request type 0x0004 carries a LIFETIME attribute (0 = destroy). 592// Build a REFRESH request. 593func nx_turn_build_refresh_request(out_buf: *u8, tx_id: *u8, 594 lifetime_sec: i64) -> i64 { 595 _turn_write_be16(out_buf, 0, NX_TURN_TYPE_REFRESH_REQUEST) 596 _turn_write_be16(out_buf, 2, 0) 597 out_buf[4] = 0x21; out_buf[5] = 0x12; out_buf[6] = 0xa4; out_buf[7] = 0x42 598 var i: i64 = 0 599 while i < 12 { out_buf[8 + i] = tx_id[i]; i = i + 1 } 600 let pos: i64 = 20 + _turn_write_lifetime(out_buf, 20, lifetime_sec) 601 _turn_write_be16(out_buf, 2, pos - 20) 602 return pos 603} 604 605// Parse REFRESH request, extract requested lifetime. 606// Returns 0 on success, non-zero error code. 607func nx_turn_parse_refresh(buf: *u8, n: i64, 608 tx_id_out: *u8, lifetime_out: *u8) -> i64 { 609 if n < 20 { return 1 } 610 if _turn_read_be16(buf, 0) != NX_TURN_TYPE_REFRESH_REQUEST { return 2 } 611 let msg_len: i64 = _turn_read_be16(buf, 2) 612 if n < 20 + msg_len { return 3 } 613 if buf[4] != 0x21 { return 4 } 614 if buf[5] != 0x12 { return 4 } 615 if buf[6] != 0xa4 { return 4 } 616 if buf[7] != 0x42 { return 4 } 617 var i: i64 = 0 618 while i < 12 { tx_id_out[i] = buf[8 + i]; i = i + 1 } 619 var pos: i64 = 20 620 let end: i64 = 20 + msg_len 621 while pos + 4 <= end { 622 let attr_type: i64 = _turn_read_be16(buf, pos) 623 let attr_len: i64 = _turn_read_be16(buf, pos + 2) 624 let val_off: i64 = pos + 4 625 if val_off + attr_len > end { return 5 } 626 if attr_type == NX_TURN_ATTR_LIFETIME { 627 if attr_len == 4 { 628 let lt: i64 = (buf[val_off] << 24) 629 | (buf[val_off + 1] << 16) 630 | (buf[val_off + 2] << 8) 631 | buf[val_off + 3] 632 lifetime_out[0] = lt & 0xff 633 lifetime_out[1] = (lt >> 8) & 0xff 634 lifetime_out[2] = (lt >> 16) & 0xff 635 lifetime_out[3] = (lt >> 24) & 0xff 636 lifetime_out[4] = 0 637 lifetime_out[5] = 0 638 lifetime_out[6] = 0 639 lifetime_out[7] = 0 640 } 641 } 642 var padded: i64 = attr_len 643 let rem: i64 = attr_len & 3 644 if rem != 0 { padded = attr_len + (4 - rem) } 645 pos = val_off + padded 646 } 647 return 0 648} 649 650// Build REFRESH success response with the chosen lifetime. 651func nx_turn_build_refresh_success(out_buf: *u8, tx_id: *u8, 652 lifetime_sec: i64) -> i64 { 653 _turn_write_be16(out_buf, 0, NX_TURN_TYPE_REFRESH_SUCCESS) 654 _turn_write_be16(out_buf, 2, 0) 655 out_buf[4] = 0x21; out_buf[5] = 0x12; out_buf[6] = 0xa4; out_buf[7] = 0x42 656 var i: i64 = 0 657 while i < 12 { out_buf[8 + i] = tx_id[i]; i = i + 1 } 658 let pos: i64 = 20 + _turn_write_lifetime(out_buf, 20, lifetime_sec) 659 _turn_write_be16(out_buf, 2, pos - 20) 660 return pos 661} 662 663// CREATE-PERMISSION (RFC 5766 §9): client whitelists peer IPs that may 664// send to this allocation. Request type 0x0008 carries 1+ XOR-PEER-ADDRESS 665// attributes (each 8-byte value). 666// 667// Build a CREATE-PERMISSION request with a single peer (most common case). 668// Multi-peer batches just call this multiple times with appended attributes. 669func nx_turn_build_create_perm_request(out_buf: *u8, tx_id: *u8, 670 peer_addr: *u8, peer_port: i64) -> i64 { 671 _turn_write_be16(out_buf, 0, NX_TURN_TYPE_CREATE_PERM_REQUEST) 672 _turn_write_be16(out_buf, 2, 0) 673 out_buf[4] = 0x21; out_buf[5] = 0x12; out_buf[6] = 0xa4; out_buf[7] = 0x42 674 var i: i64 = 0 675 while i < 12 { out_buf[8 + i] = tx_id[i]; i = i + 1 } 676 let pos: i64 = 20 + _turn_write_xor_address(out_buf, 20, 677 NX_TURN_ATTR_XOR_PEER_ADDR, peer_addr, peer_port) 678 _turn_write_be16(out_buf, 2, pos - 20) 679 return pos 680} 681 682// Build CREATE-PERMISSION success response (empty body). 683func nx_turn_build_create_perm_success(out_buf: *u8, tx_id: *u8) -> i64 { 684 _turn_write_be16(out_buf, 0, NX_TURN_TYPE_CREATE_PERM_SUCCESS) 685 _turn_write_be16(out_buf, 2, 0) 686 out_buf[4] = 0x21; out_buf[5] = 0x12; out_buf[6] = 0xa4; out_buf[7] = 0x42 687 var i: i64 = 0 688 while i < 12 { out_buf[8 + i] = tx_id[i]; i = i + 1 } 689 return 20 690} 691 692// CHANNEL-BIND (RFC 5766 §11): client binds a 16-bit channel number to a 693// specific peer, so subsequent data uses 4-byte ChannelData header instead 694// of the ~50-byte SEND/DATA overhead. Huge bandwidth win for voice. 695// 696// Channel numbers must be in [0x4000, 0x7FFF] per spec. 697// Build a CHANNEL-BIND request: CHANNEL-NUMBER + XOR-PEER-ADDRESS. 698func nx_turn_build_channel_bind_request(out_buf: *u8, tx_id: *u8, 699 channel_num: i64, 700 peer_addr: *u8, peer_port: i64) -> i64 { 701 _turn_write_be16(out_buf, 0, NX_TURN_TYPE_CHANNEL_BIND_REQUEST) 702 _turn_write_be16(out_buf, 2, 0) 703 out_buf[4] = 0x21; out_buf[5] = 0x12; out_buf[6] = 0xa4; out_buf[7] = 0x42 704 var i: i64 = 0 705 while i < 12 { out_buf[8 + i] = tx_id[i]; i = i + 1 } 706 var pos: i64 = 20 707 // CHANNEL-NUMBER attribute (4-byte value: 2-byte channel + 2-byte RFFU) 708 _turn_write_be16(out_buf, pos, NX_TURN_ATTR_CHANNEL_NUMBER) 709 _turn_write_be16(out_buf, pos + 2, 4) 710 _turn_write_be16(out_buf, pos + 4, channel_num) 711 out_buf[pos + 6] = 0 712 out_buf[pos + 7] = 0 713 pos = pos + 8 714 pos = pos + _turn_write_xor_address(out_buf, pos, 715 NX_TURN_ATTR_XOR_PEER_ADDR, peer_addr, peer_port) 716 _turn_write_be16(out_buf, 2, pos - 20) 717 return pos 718} 719 720// Build CHANNEL-BIND success response (empty body). 721func nx_turn_build_channel_bind_success(out_buf: *u8, tx_id: *u8) -> i64 { 722 _turn_write_be16(out_buf, 0, NX_TURN_TYPE_CHANNEL_BIND_SUCCESS) 723 _turn_write_be16(out_buf, 2, 0) 724 out_buf[4] = 0x21; out_buf[5] = 0x12; out_buf[6] = 0xa4; out_buf[7] = 0x42 725 var i: i64 = 0 726 while i < 12 { out_buf[8 + i] = tx_id[i]; i = i + 1 } 727 return 20 728} 729 730// ChannelData (RFC 5766 §11.4): the compact data path. 731// 4-byte header: 732// [16-bit channel number, in 0x4000..0x7FFF] 733// [16-bit length of application data] 734// [application data, padded to 4-byte alignment over UDP] 735// 736// Build a ChannelData message. Returns total bytes written. 737func nx_turn_build_channel_data(out_buf: *u8, 738 channel_num: i64, 739 payload: *u8, payload_len: i64) -> i64 { 740 _turn_write_be16(out_buf, 0, channel_num) 741 _turn_write_be16(out_buf, 2, payload_len) 742 var i: i64 = 0 743 while i < payload_len { 744 out_buf[4 + i] = payload[i] 745 i = i + 1 746 } 747 var total: i64 = 4 + payload_len 748 let rem: i64 = total & 3 749 if rem != 0 { 750 let pad: i64 = 4 - rem 751 var p: i64 = 0 752 while p < pad { out_buf[total + p] = 0; p = p + 1 } 753 total = total + pad 754 } 755 return total 756} 757 758// Parse ChannelData: extract channel number + payload. 759// Returns 0 on success, non-zero on bad format. 760func nx_turn_parse_channel_data(buf: *u8, n: i64, 761 channel_num_out: *u8, 762 payload_out: *u8, 763 payload_len_out: *u8) -> i64 { 764 if n < 4 { return 1 } 765 let cnum: i64 = _turn_read_be16(buf, 0) 766 if cnum < 0x4000 { return 2 } 767 if cnum > 0x7fff { return 2 } 768 let plen: i64 = _turn_read_be16(buf, 2) 769 if n < 4 + plen { return 3 } 770 channel_num_out[0] = cnum & 0xff 771 channel_num_out[1] = (cnum >> 8) & 0xff 772 var i: i64 = 0 773 while i < plen { payload_out[i] = buf[4 + i]; i = i + 1 } 774 payload_len_out[0] = plen & 0xff 775 payload_len_out[1] = (plen >> 8) & 0xff 776 payload_len_out[2] = 0 777 payload_len_out[3] = 0 778 return 0 779} 780 781// Combined Phase A.3 smoke: REFRESH + CREATE-PERMISSION + CHANNEL-BIND 782// + ChannelData all round-trip byte-exact. 783// Returns 0 on full success, non-zero bitmap on failure. 784func nx_turn_phase_a3_smoke(scratch: *u8) -> i64 { 785 let buf: *u8 = scratch 786 let tx_id: *u8 = (scratch as i64 + 4096) as *u8 787 let peer_addr: *u8 = (scratch as i64 + 4128) as *u8 788 let payload: *u8 = (scratch as i64 + 4160) as *u8 // 64 B 789 let chan_out: *u8 = (scratch as i64 + 4256) as *u8 // 8 B (past payload+64) 790 let plen_out: *u8 = (scratch as i64 + 4272) as *u8 // 8 B 791 let pay_out: *u8 = (scratch as i64 + 4288) as *u8 // 64 B (no overlap with payload) 792 let lt_out: *u8 = (scratch as i64 + 4400) as *u8 // 8 B 793 let tx_out: *u8 = (scratch as i64 + 4416) as *u8 // 12 B 794 795 var i: i64 = 0 796 while i < 12 { tx_id[i] = 0xB0 + i; i = i + 1 } 797 peer_addr[0] = 192; peer_addr[1] = 168; peer_addr[2] = 1; peer_addr[3] = 50 798 var pi: i64 = 0 799 while pi < 64 { payload[pi] = (pi * 13 + 5) & 0xff; pi = pi + 1 } 800 801 var fail: i64 = 0 802 803 // REFRESH round-trip 804 let refresh_len: i64 = nx_turn_build_refresh_request(buf, tx_id, 1800) 805 let r_rc: i64 = nx_turn_parse_refresh(buf, refresh_len, tx_out, lt_out) 806 if r_rc != 0 { fail = fail | 1 } 807 let lt: i64 = lt_out[0] | (lt_out[1] << 8) | (lt_out[2] << 16) | (lt_out[3] << 24) 808 if lt != 1800 { fail = fail | 2 } 809 var tj: i64 = 0 810 while tj < 12 { 811 if tx_id[tj] != tx_out[tj] { fail = fail | 4 } 812 tj = tj + 1 813 } 814 815 // CREATE-PERMISSION builds (parse side reuses parse_indication helper structure; 816 // for smoke we verify the build is well-formed by checking it has the right 817 // type code, magic, length field, and peer XOR matches by-hand). 818 let cp_len: i64 = nx_turn_build_create_perm_request(buf, tx_id, peer_addr, 12345) 819 if _turn_read_be16(buf, 0) != NX_TURN_TYPE_CREATE_PERM_REQUEST { fail = fail | 8 } 820 if _turn_read_be16(buf, 2) != 12 { fail = fail | 16 } // 12-byte XOR-PEER-ADDRESS 821 if cp_len != 32 { fail = fail | 32 } // 20 header + 12 attr 822 823 // CHANNEL-BIND build + structural check 824 let cb_len: i64 = nx_turn_build_channel_bind_request(buf, tx_id, 0x4001, 825 peer_addr, 12345) 826 if _turn_read_be16(buf, 0) != NX_TURN_TYPE_CHANNEL_BIND_REQUEST { fail = fail | 64 } 827 if cb_len != 40 { fail = fail | 128 } // 20 + 8 (CHANNEL-NUMBER) + 12 (XOR-PEER) 828 829 // ChannelData round-trip: build then parse 830 let cd_len: i64 = nx_turn_build_channel_data(buf, 0x4001, payload, 64) 831 let cd_rc: i64 = nx_turn_parse_channel_data(buf, cd_len, chan_out, pay_out, plen_out) 832 if cd_rc != 0 { fail = fail | 256 } 833 let cnum: i64 = chan_out[0] | (chan_out[1] << 8) 834 if cnum != 0x4001 { fail = fail | 512 } 835 let plen: i64 = plen_out[0] | (plen_out[1] << 8) 836 if plen != 64 { fail = fail | 1024 } 837 var pk: i64 = 0 838 while pk < 64 { 839 if payload[pk] != pay_out[pk] { fail = fail | 2048 } 840 pk = pk + 1 841 } 842 return fail 843} 844 845// ============================================================================ 846// MESSAGE-INTEGRITY moved to nx_turn_msgintegrity.nx (see header). 847// ============================================================================ 848// (former MI section deleted -- importers needing MI should import 849// nx_turn_msgintegrity.nx which depends on this file + nx_hmac_sha1.) 850// 851// Stub kept-comment for context: 852// HMAC-SHA1 over the message, with key = SASLprep(password) for short-term 853// creds. Wire format per RFC 5389 §15.4: 20-byte MAC in a 24-byte attribute. 854// The HMAC input is the message UP TO BUT NOT INCLUDING the MI attribute, 855// with the Length field set as if MI were included (+24 bytes). 856 857// (Former MI implementation deleted; see nx_turn_msgintegrity.nx) 858 859// Smoke export: build ALLOCATE success response, verify structure. 860// Returns 0 on success, non-zero on bad structure. 861func nx_turn_build_success_test(scratch: *u8) -> i64 { 862 let buf: *u8 = scratch 863 let tx_id: *u8 = (scratch as i64 + 2048) as *u8 864 let relay_addr: *u8 = (scratch as i64 + 2080) as *u8 865 let mapped_addr: *u8 = (scratch as i64 + 2096) as *u8 866 var ti: i64 = 0 867 while ti < 12 { tx_id[ti] = ti + 1; ti = ti + 1 } 868 // Synthetic addresses: 192.168.8.227 and 73.14.222.50 869 relay_addr[0] = 192; relay_addr[1] = 168; relay_addr[2] = 8; relay_addr[3] = 227 870 mapped_addr[0] = 73; mapped_addr[1] = 14; mapped_addr[2] = 222; mapped_addr[3] = 50 871 let total: i64 = nx_turn_build_allocate_success(buf, tx_id, 872 relay_addr, 49160, mapped_addr, 51820, 600) 873 // Expected: 20 header + 12 XOR-RELAYED + 8 LIFETIME + 12 XOR-MAPPED = 52 874 if total != 52 { return 1 } 875 // Verify magic cookie 876 if buf[4] != 0x21 { return 2 } 877 if buf[5] != 0x12 { return 3 } 878 // Verify type 879 if _turn_read_be16(buf, 0) != NX_TURN_TYPE_ALLOCATE_SUCCESS { return 4 } 880 // Verify length field 881 if _turn_read_be16(buf, 2) != 32 { return 5 } // 12 + 8 + 12 = 32 882 return 0 883}