1#![no_std]
2#![no_main]
3#![feature(alloc_error_handler)]
4
5extern crate alloc;
6
7use alloc::format;
8use core::{alloc::Layout, fmt::Write, panic::PanicInfo};
9use strat9_syscall::{call, data::TimeSpec, number};
10
11const DEFAULT_STUN_HOST: &str = "stun.l.google.com";
13const DEFAULT_STUN_PORT: u16 = 19302;
15
16const ICE_HOST_PRIORITY: u32 = (126u32 << 24) | (65535u32 << 8) | (256 - 1);
19const ICE_SRFLX_PRIORITY: u32 = (100u32 << 24) | (65535u32 << 8) | (256 - 1);
20
21const O_RDONLY: usize = 0x1;
23const O_RDWR: usize = 0x3;
24
25alloc_freelist::define_freelist_allocator!(pub struct BumpAllocator; heap_size = 64 * 1024;);
26
27#[global_allocator]
28static GLOBAL_ALLOCATOR: BumpAllocator = BumpAllocator;
29
30#[alloc_error_handler]
31fn alloc_error(_layout: Layout) -> ! {
32 let _ = call::write(1, b"[ice-candidate] OOM\n");
33 call::exit(12)
34}
35
36#[panic_handler]
37fn panic(info: &PanicInfo) -> ! {
38 let _ = call::write(1, b"[ice-candidate] PANIC: ");
39 let mut buf = [0u8; 192];
40 let mut w = BufWriter {
41 buf: &mut buf,
42 pos: 0,
43 };
44 let _ = write!(w, "{}", info.message());
45 let len = w.pos;
46 drop(w); if len > 0 {
48 let _ = call::write(1, &buf[..len]);
49 }
50 let _ = call::write(1, b"\n");
51 call::exit(255)
52}
53
54struct BufWriter<'a> {
55 buf: &'a mut [u8],
56 pos: usize,
57}
58
59impl core::fmt::Write for BufWriter<'_> {
60 fn write_str(&mut self, s: &str) -> core::fmt::Result {
61 let bytes = s.as_bytes();
62 let avail = self.buf.len().saturating_sub(self.pos);
63 let n = bytes.len().min(avail);
64 self.buf[self.pos..self.pos + n].copy_from_slice(&bytes[..n]);
65 self.pos += n;
66 Ok(())
67 }
68}
69
70fn log(msg: &str) {
71 let _ = call::write(1, msg.as_bytes());
72}
73
74fn sleep_ms(ms: u64) {
75 let req = TimeSpec {
76 tv_sec: (ms / 1000) as i64,
77 tv_nsec: ((ms % 1000) * 1_000_000) as i64,
78 };
79 let _ = unsafe {
80 strat9_syscall::syscall2(number::SYS_NANOSLEEP, &req as *const TimeSpec as usize, 0)
81 };
82}
83
84fn scheme_read(path: &str, buf: &mut [u8]) -> Result<usize, ()> {
85 let fd = call::openat(0, path, O_RDONLY, 0).map_err(|_| ())?;
86 let n = call::read(fd as usize, buf).map_err(|_| {
87 let _ = call::close(fd as usize);
88 })?;
89 let _ = call::close(fd as usize);
90 Ok(n)
91}
92
93fn scheme_open(path: &str, flags: usize) -> Result<usize, ()> {
94 call::openat(0, path, flags, 0).map_err(|_| ())
95}
96
97fn parse_ipv4_literal(s: &str) -> bool {
98 let bytes = s.as_bytes();
99 if bytes.is_empty() {
100 return false;
101 }
102 let mut dots = 0usize;
103 let mut val: u16 = 0;
104 let mut has_digit = false;
105 for &b in bytes {
106 if b == b'.' {
107 if !has_digit || val > 255 || dots >= 3 {
108 return false;
109 }
110 dots += 1;
111 val = 0;
112 has_digit = false;
113 continue;
114 }
115 if !b.is_ascii_digit() {
116 return false;
117 }
118 val = val * 10 + (b - b'0') as u16;
119 has_digit = true;
120 }
121 has_digit && val <= 255 && dots == 3
122}
123
124fn resolve_target<'a>(target: &'a str, resolved_buf: &'a mut [u8; 64]) -> Option<&'a str> {
125 if parse_ipv4_literal(target) {
126 return Some(target);
127 }
128 let path = format!("/net/resolve/{}", target);
129 let n = scheme_read(&path, resolved_buf).ok()?;
130 if n == 0 {
131 return None;
132 }
133 let end = resolved_buf[..n]
134 .iter()
135 .position(|&b| b == b'\n')
136 .unwrap_or(n);
137 if end == 0 {
138 return None;
139 }
140 let resolved = core::str::from_utf8(&resolved_buf[..end]).ok()?;
141 if parse_ipv4_literal(resolved) {
142 Some(resolved)
143 } else {
144 None
145 }
146}
147
148fn parse_u16_decimal(s: &[u8]) -> Option<u16> {
149 if s.is_empty() {
150 return None;
151 }
152 let mut v: u32 = 0;
153 for &b in s {
154 if !b.is_ascii_digit() {
155 return None;
156 }
157 v = v * 10 + (b - b'0') as u32;
158 if v > 65535 {
159 return None;
160 }
161 }
162 Some(v as u16)
163}
164
165fn read_stun_config<'a>(host_buf: &'a mut [u8; 253], port_out: &mut u16) -> &'a str {
173 let mut raw = [0u8; 260];
174 let n = match scheme_read("/net/stun-config", &mut raw) {
175 Ok(n) if n > 0 => n,
176 _ => return DEFAULT_STUN_HOST,
177 };
178 let mut end = n;
180 while end > 0 && (raw[end - 1] == b'\n' || raw[end - 1] == b'\r' || raw[end - 1] == b' ') {
181 end -= 1;
182 }
183 let line = &raw[..end];
184 let colon = line.iter().rposition(|&b| b == b':');
186 let (host_bytes, port_bytes) = if let Some(pos) = colon {
187 (&line[..pos], Some(&line[pos + 1..]))
188 } else {
189 (line, None)
190 };
191 if host_bytes.is_empty() || host_bytes.len() > 253 {
192 return DEFAULT_STUN_HOST;
193 }
194 if let Some(pb) = port_bytes {
195 if let Some(p) = parse_u16_decimal(pb) {
196 *port_out = p;
197 } else {
198 return DEFAULT_STUN_HOST;
199 }
200 }
201 host_buf[..host_bytes.len()].copy_from_slice(host_bytes);
202 match core::str::from_utf8(&host_buf[..host_bytes.len()]) {
203 Ok(s) => s,
204 Err(_) => DEFAULT_STUN_HOST,
205 }
206}
207
208fn read_local_ip<'a>(out: &'a mut [u8; 64]) -> Option<&'a str> {
209 let n = scheme_read("/net/address", out).ok()?;
210 if n == 0 {
211 return None;
212 }
213 let mut end = out[..n].iter().position(|&b| b == b'\n').unwrap_or(n);
214 if let Some(slash) = out[..end].iter().position(|&b| b == b'/') {
215 end = slash;
216 }
217 if end == 0 {
218 return None;
219 }
220 let ip = core::str::from_utf8(&out[..end]).ok()?;
221 if parse_ipv4_literal(ip) {
222 Some(ip)
223 } else {
224 None
225 }
226}
227
228fn parse_stun_binding(resp: &[u8], txid: &[u8; 12]) -> Option<([u8; 4], u16)> {
229 if resp.len() < 20 {
230 return None;
231 }
232 let msg_type = u16::from_be_bytes([resp[0], resp[1]]);
233 if msg_type != 0x0101 {
234 return None;
235 }
236 let msg_len = u16::from_be_bytes([resp[2], resp[3]]) as usize;
237 if msg_len + 20 > resp.len() {
238 return None;
239 }
240 if resp[4..8] != [0x21, 0x12, 0xA4, 0x42] {
241 return None;
242 }
243 if resp[8..20] != txid[..] {
244 return None;
245 }
246 let mut off = 20usize;
247 let end = 20 + msg_len;
248 while off + 4 <= end && off + 4 <= resp.len() {
249 let attr_ty = u16::from_be_bytes([resp[off], resp[off + 1]]);
250 let attr_len = u16::from_be_bytes([resp[off + 2], resp[off + 3]]) as usize;
251 let val_off = off + 4;
252 let val_end = val_off + attr_len;
253 if val_end > end || val_end > resp.len() {
254 return None;
255 }
256 if attr_ty == 0x0020 && attr_len >= 8 {
257 if resp[val_off + 1] != 0x01 {
258 return None;
259 }
260 let xport = u16::from_be_bytes([resp[val_off + 2], resp[val_off + 3]]);
261 let port = xport ^ 0x2112;
262 let ip = [
263 resp[val_off + 4] ^ 0x21,
264 resp[val_off + 5] ^ 0x12,
265 resp[val_off + 6] ^ 0xA4,
266 resp[val_off + 7] ^ 0x42,
267 ];
268 return Some((ip, port));
269 }
270 if attr_ty == 0x0001 && attr_len >= 8 {
271 if resp[val_off + 1] != 0x01 {
272 return None;
273 }
274 let port = u16::from_be_bytes([resp[val_off + 2], resp[val_off + 3]]);
275 let ip = [
276 resp[val_off + 4],
277 resp[val_off + 5],
278 resp[val_off + 6],
279 resp[val_off + 7],
280 ];
281 return Some((ip, port));
282 }
283 let pad = (4 - (attr_len % 4)) % 4;
284 off = val_end + pad;
285 }
286 None
287}
288
289#[unsafe(no_mangle)]
290pub extern "C" fn _start() -> ! {
291 let mut stun_port: u16 = DEFAULT_STUN_PORT;
292 let mut stun_host_buf = [0u8; 253];
293 let stun_host = read_stun_config(&mut stun_host_buf, &mut stun_port);
294 let mut resolved = [0u8; 64];
295 let Some(stun_ip) = resolve_target(stun_host, &mut resolved) else {
296 log("[ice-candidate] resolve failed\n");
297 call::exit(1);
298 };
299
300 let path = format!("/net/udp/connect/{}/{}", stun_ip, stun_port);
301 let mut req = [0u8; 20];
302 req[0..2].copy_from_slice(&0x0001u16.to_be_bytes());
303 req[2..4].copy_from_slice(&0u16.to_be_bytes());
304 req[4..8].copy_from_slice(&0x2112A442u32.to_be_bytes());
305 let now = unsafe { strat9_syscall::syscall0(number::SYS_CLOCK_GETTIME) }.unwrap_or(0) as u64;
306 let tid = now.to_be_bytes();
307 req[8..16].copy_from_slice(&tid);
308 req[16..20].copy_from_slice(&[0x53, 0x49, 0x4C, 0x4F]);
309 let mut txid = [0u8; 12];
310 txid.copy_from_slice(&req[8..20]);
311
312 let fd = match scheme_open(&path, O_RDWR) {
313 Ok(fd) => fd,
314 Err(_) => {
315 log("[ice-candidate] stun open failed\n");
316 call::exit(2);
317 }
318 };
319
320 if call::write(fd as usize, &req).is_err() {
321 log("[ice-candidate] stun send failed\n");
322 let _ = call::close(fd as usize);
323 call::exit(2);
324 }
325
326 let mut resp = [0u8; 128];
327 let mut mapped: Option<([u8; 4], u16)> = None;
328 let mut tries = 0usize;
329 while tries < 50 {
330 tries += 1;
331 if let Ok(n) = call::read(fd as usize, &mut resp) {
332 if n > 0 {
333 mapped = parse_stun_binding(&resp[..n], &txid);
334 if mapped.is_some() {
335 break;
336 }
337 }
338 }
339 sleep_ms(20);
340 }
341
342 let mut local_ip_buf = [0u8; 64];
343 if let Some(local_ip) = read_local_ip(&mut local_ip_buf) {
344 let host = format!(
346 "candidate:1 1 UDP {} {} 0 typ host\r\n",
347 ICE_HOST_PRIORITY, local_ip
348 );
349 log(&host);
350 }
351
352 if let Some((ip, port)) = mapped {
353 let srflx = format!(
354 "candidate:2 1 UDP {} {}.{}.{}.{} {} typ srflx raddr 0.0.0.0 rport 0\r\n",
356 ICE_SRFLX_PRIORITY, ip[0], ip[1], ip[2], ip[3], port
357 );
358 log(&srflx);
359 let _ = call::close(fd as usize);
360 call::exit(0);
361 }
362
363 log("[ice-candidate] no srflx candidate\n");
364 let _ = call::close(fd as usize);
365 call::exit(3)
366}