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