code wiki / (root) / nx_stun_client.nx

nx_stun_client.nx source

↩ module page · 133 lines · 6074 B

1// nx_stun_client.nx -- SOVEREIGN STUN client (server-reflexive address discovery), NishiLang, composing 2// nx_stun. The gather side of ICE: send a Binding Request to a STUN server, read the Binding Response, and 3// extract the XOR-MAPPED-ADDRESS -- your PUBLIC reflexive IP:port as the server saw it. Works against ANY 4// standard STUN server (ours OR a third-party one), proving byte-level interop, with ZERO third-party code. 5// 6// sc_query(...) is the reusable primitive (real UDP socket round-trip, bounded by a timeout so it never 7// hangs). main() is a CLI that prints the reflexive address as JSON -- registered as the MCP tool 8// `nx_stun_probe` so an agent/operator can discover a public address over the wire. 9// license_tier: INDEPENDENT_REDERIVE genealogy_id: international-research-sources/ietf/rfc_5389 10import "nx_syscalls.nx" 11import "nx_stun.nx" 12const K_MAGIC_2048: i64 = 2048 13 14// walk the response attributes looking for XOR-MAPPED-ADDRESS (0x0020). Returns the byte offset of its 15// VALUE (family/port/addr), or -1 if absent. 16func sc_find_xma(resp: *u8, n: i64) -> i64 { 17 let mlen: i64 = st_len(resp) 18 var end: i64 = 20 + mlen 19 if end > n { end = n } 20 var off: i64 = 20 21 while off + 4 <= end { 22 let atype: i64 = st_get16(resp, off) 23 let alen: i64 = st_get16(resp, off+2) 24 if atype == STUN_ATTR_XOR_MAPPED_ADDRESS { return off + 4 } 25 off = off + 4 + ((alen+3)/4)*4 26 } 27 return 0 - 1 28} 29 30// build a 16-byte sockaddr_in for i0.i1.i2.i3:port (network byte order). 31func sc_sockaddr(sa: *u8, i0: i64, i1: i64, i2: i64, i3: i64, port: i64) -> i64 { 32 sa[0]=2 as u8; sa[1]=0 as u8 33 sa[2]=((port>>8)&0xff) as u8; sa[3]=(port&0xff) as u8 34 sa[4]=i0 as u8; sa[5]=i1 as u8; sa[6]=i2 as u8; sa[7]=i3 as u8 35 var k: i64=8; while k<16 { sa[k]=0 as u8; k=k+1 } 36 return 0 37} 38 39// QUERY a STUN server. On success writes the reflexive addr (u32) + port into out_ip/out_port and returns 0. 40// Negative return: -1 timeout/no-response, -2 not a Binding Response, -3 no XOR-MAPPED-ADDRESS, -9 socket. 41func sc_query(i0: i64, i1: i64, i2: i64, i3: i64, port: i64, out_ip: *i64, out_port: *i64) -> i64 { 42 let dest: *u8 = sys_mmap(16) 43 sc_sockaddr(dest, i0, i1, i2, i3, port) 44 let fd: i64 = sys_socket(AF_INET, SOCK_DGRAM, 0) 45 if fd < 0 { return 0 - 9 } 46 sys_set_socket_timeout(fd, 3) 47 // Binding Request with a time-seeded transaction id (fresh socket, so matching is trivial). 48 let txid: *u8 = sys_mmap(16) 49 var seed: i64 = sys_now_realtime_ms() 50 var ti: i64 = 0 51 while ti < 12 { txid[ti] = (seed & 0xff) as u8; seed = seed / 7 + 131; ti = ti + 1 } 52 let req: *u8 = sys_mmap(64) 53 st_write_header(req, STUN_BINDING_REQUEST, 0, txid) 54 sys_sendto(fd, req, 20, 0, dest, 16) 55 let resp: *u8 = sys_mmap(K_MAGIC_2048) 56 let n: i64 = sys_recvfrom(fd, resp, K_MAGIC_2048, 0, 0 as *u8, 0 as *i64) 57 sys_close(fd) 58 if n < 20 { return 0 - 1 } 59 if st_type(resp) != STUN_BINDING_RESPONSE { return 0 - 2 } 60 let xoff: i64 = sc_find_xma(resp, n) 61 if xoff < 0 { return 0 - 3 } 62 let xma: *u8 = (resp as i64 + xoff) as *u8 63 out_ip[0] = st_xma_addr(xma) 64 out_port[0] = st_xma_port(xma) 65 return 0 66} 67 68// ------------------------------------------------------------------ CLI (MCP tool nx_stun_probe) 69func cp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 70func cpn(v: i64) -> i64 { 71 let b: *u8=sys_mmap(28); var m: i64=v; if m<0 {sys_write(1,"-" as *u8,1); m=0-m} 72 let t: *u8=sys_mmap(28); var k: i64=0; if m==0 {t[0]=48 as u8;k=1} while m>0 {t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} 73 var i: i64=0; while i<k {b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 74} 75// parse a dotted-quad "a.b.c.d" from a z-string into oct[0..4]. Returns 1 ok, 0 malformed. 76func cp_parse_ip(s: *u8, oct: *i64) -> i64 { 77 var idx: i64=0 78 var cur: i64=0 79 var have: i64=0 80 var i: i64=0 81 var go: i64=1 82 while go==1 { 83 let c: i64 = s[i] as i64 & 0xff 84 if c==0 { 85 if idx>3 { return 0 } 86 oct[idx]=cur; idx=idx+1; go=0 87 } else { 88 if c==46 { // '.' 89 if idx>3 { return 0 } 90 if have==0 { return 0 } 91 oct[idx]=cur; idx=idx+1; cur=0; have=0 92 } else { 93 if c<48 { return 0 } 94 if c>57 { return 0 } 95 cur=cur*10+(c-48); have=1 96 if cur>255 { return 0 } 97 } 98 i=i+1 99 } 100 } 101 if idx!=4 { return 0 } 102 return 1 103} 104func cp_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8){ let c: i64=s[i] as i64 & 0xff; if c>=48 { if c<=57 { v=v*10+(c-48) } } i=i+1 } return v } 105 106func main(argc: i64, argv: *i64) -> i64 { 107 if argc < 3 { 108 cp("{\"v\":1,\"ok\":0,\"error\":\"usage: nx_stun_probe <server-ipv4> <port>\"}\n" as *u8) 109 sys_exit(2); return 2 110 } 111 let host: *u8 = argv[1] as *u8 112 let ports: *u8 = argv[2] as *u8 113 let oct: *i64 = sys_mmap(64) as *i64 114 if cp_parse_ip(host, oct)==0 { 115 cp("{\"v\":1,\"ok\":0,\"error\":\"server must be a dotted-quad IPv4 (hostname resolution is a follow-on)\"}\n" as *u8) 116 sys_exit(2); return 2 117 } 118 let port: i64 = cp_atoi(ports) 119 let oip: *i64 = sys_mmap(16) as *i64 120 let oport: *i64 = sys_mmap(16) as *i64 121 let rc: i64 = sc_query(oct[0], oct[1], oct[2], oct[3], port, oip, oport) 122 if rc != 0 { 123 cp("{\"v\":1,\"ok\":0,\"rc\":" as *u8); cpn(rc); cp(",\"error\":\"no XOR-MAPPED-ADDRESS (timeout / not a STUN server / unreachable)\"}\n" as *u8) 124 sys_exit(1); return 1 125 } 126 let a: i64 = oip[0] 127 cp("{\"v\":1,\"ok\":1,\"server\":\"" as *u8); cpn(oct[0]); cp("." as *u8); cpn(oct[1]); cp("." as *u8); cpn(oct[2]); cp("." as *u8); cpn(oct[3]); cp(":" as *u8); cpn(port) 128 cp("\",\"reflexive\":\"" as *u8) 129 cpn((a>>24)&0xff); cp("." as *u8); cpn((a>>16)&0xff); cp("." as *u8); cpn((a>>8)&0xff); cp("." as *u8); cpn(a&0xff); cp(":" as *u8); cpn(oport[0]) 130 cp("\"}\n" as *u8) 131 sys_exit(0) 132 return 0 133}