1use super::*;
4
5pub(crate) fn builtin_net(_env: &mut Env, _args: &[AST]) -> Result<Value, String> {
6 Ok(Value::Str(alloc::format!(
7 "NET:\n{}",
8 crate::kernel::net::status_line()
9 )))
10}
11
12pub(crate) fn builtin_nic(_env: &mut Env, _args: &[AST]) -> Result<Value, String> {
13 Ok(Value::Str(crate::kernel::net::nic_debug_info()))
14}
15
16pub(crate) fn builtin_dhcp(env: &mut Env, args: &[AST]) -> Result<Value, String> {
17 let vals = eval_args(env, args)?;
18 let cmd = if vals.is_empty() {
19 String::from("show")
20 } else {
21 unquote(&vals[0])
22 };
23
24 match cmd.as_str() {
25 "show" => Ok(Value::Str(alloc::format!(
26 "DHCP: {}",
27 crate::kernel::net::dhcp_status_line()
28 ))),
29 "start" => match crate::kernel::net::dhcp_start_async(false) {
30 Ok(()) => Ok(Value::Str(String::from(
31 "DHCP start scheduled\nhint: run 'dhcp(\"show\")' to watch state",
32 ))),
33 Err(e) => Err(alloc::format!("DHCP error: {}", e)),
34 },
35 "renew" => match crate::kernel::net::dhcp_start_async(true) {
36 Ok(()) => Ok(Value::Str(String::from(
37 "DHCP renew scheduled\nhint: run 'dhcp(\"show\")' to watch state",
38 ))),
39 Err(e) => Err(alloc::format!("DHCP error: {}", e)),
40 },
41 "release" => {
42 crate::kernel::net::dhcp_release();
43 Ok(Value::Str(String::from("DHCP released")))
44 }
45 _ => Err(String::from(
46 "USAGE: dhcp([\"show\"|\"start\"|\"renew\"|\"release\"])",
47 )),
48 }
49}
50
51pub(crate) fn builtin_arp(env: &mut Env, args: &[AST]) -> Result<Value, String> {
52 let vals = eval_args(env, args)?;
53 let cmd = if vals.is_empty() {
54 String::from("show")
55 } else {
56 unquote(&vals[0])
57 };
58
59 match cmd.as_str() {
60 "show" => {
61 let lines = crate::kernel::net::arp_cache_lines();
62 if lines.is_empty() {
63 Ok(Value::Str(String::from("ARP cache is empty")))
64 } else {
65 let mut out = String::from("ARP cache:\n");
66 for line in lines {
67 out.push_str(&alloc::format!(" - {}\n", line));
68 }
69 Ok(Value::Str(out))
70 }
71 }
72 "whohas" => {
73 if vals.len() < 2 {
74 return Err(String::from("USAGE: arp(\"whohas\" \"ip\")"));
75 }
76 let ip_s = unquote(&vals[1]);
77 let target_ip = match crate::kernel::net::parse_ipv4(&ip_s) {
78 Some(ip) => ip,
79 None => return Err(String::from("ERROR: invalid IPv4 address")),
80 };
81 let mut frame = [0u8; 64];
82 match crate::kernel::net::build_arp_request(target_ip, &mut frame) {
83 Ok(len) => Ok(Value::Str(alloc::format!(
84 "ARP who-has {}? request built ({} bytes)",
85 crate::kernel::net::format_ipv4(target_ip),
86 len
87 ))),
88 Err(e) => Err(alloc::format!("ARP error: {}", e)),
89 }
90 }
91 _ => Err(String::from(
92 "USAGE: arp([\"show\"|\"whohas\"] ...)",
93 )),
94 }
95}
96
97pub(crate) fn builtin_netbench(env: &mut Env, args: &[AST]) -> Result<Value, String> {
108 let vals = eval_args(env, args)?;
109 if vals.is_empty() {
110 return Err(String::from("USAGE: netbench(\"https://host/path\", n)"));
111 }
112 let url = unquote(&vals[0]);
113 let n: u32 = if vals.len() > 1 {
114 unquote(&vals[1]).trim().parse().unwrap_or(5)
115 } else {
116 5
117 };
118 let n = n.clamp(1, 50);
119
120 let rest = url.strip_prefix("https://").unwrap_or(&url);
121 let (host, path) = match rest.find('/') {
124 Some(i) => (
125 rest.get(..i).unwrap_or(rest),
126 rest.get(i..).unwrap_or("/"),
127 ),
128 None => (rest, "/"),
129 };
130
131 let mut ok = 0u32;
132 let mut bytes_total = 0usize;
133 let gap_s: u32 = if vals.len() > 2 {
137 unquote(&vals[2]).trim().parse().unwrap_or(0)
138 } else {
139 0
140 };
141
142 for i in 0..n {
143 if i > 0 && gap_s > 0 {
144 crate::kernel::scheduler::sleep((gap_s as usize) * 100);
145 }
146 crate::warn!(
151 "[NETBENCH][RES] before={} rx_frames={} eth(ipv4/arp/other/err)={:?} tcp_sockets={} udp_queue={} arp={}",
152 i + 1,
153 crate::kernel::usb::RX_FRAME_COUNT.load(core::sync::atomic::Ordering::Relaxed),
154 crate::kernel::net::arp::eth_dispatch_counts(),
155 crate::kernel::net::tcp_socket_lines().len(),
156 crate::kernel::net::udp_queue_lines(None).len(),
157 crate::kernel::net::arp_cache_lines().len()
158 );
159 match crate::kernel::tls::https_get_binary(host, path) {
160 Ok(b) => {
161 ok += 1;
162 bytes_total += b.len();
163 crate::warn!("[NETBENCH] {}/{} OK {} bytes", i + 1, n, b.len());
164 }
165 Err(e) => {
166 crate::warn!("[NETBENCH] {}/{} FAILED {}", i + 1, n, e);
167 }
168 }
169 }
170 crate::kernel::tls::stats::report("netbench-done");
171 Ok(Value::Str(alloc::format!(
172 "netbench {}{}: {}/{} ok, {} bytes total (段階別内訳は [NET][STATS] を参照)",
173 host, path, ok, n, bytes_total
174 )))
175}
176
177pub(crate) fn builtin_dns(env: &mut Env, args: &[AST]) -> Result<Value, String> {
178 let vals = eval_args(env, args)?;
179 if vals.is_empty() {
180 return Err(String::from("USAGE: dns(\"host\")"));
181 }
182 let host = unquote(&vals[0]);
183 match crate::kernel::net::dns_query_a_via_udp(&host) {
184 Ok(ip) => Ok(Value::Str(alloc::format!(
185 "DNS: {} -> {} (via UDP path)",
186 host,
187 crate::kernel::net::format_ipv4(ip)
188 ))),
189 Err(e) => Err(alloc::format!("DNS query error: {}", e)),
190 }
191}
192
193pub(crate) fn builtin_ntp(env: &mut Env, args: &[AST]) -> Result<Value, String> {
194 let vals = eval_args(env, args)?;
195 let host = if vals.is_empty() {
197 String::from("ntp.nict.jp")
198 } else {
199 unquote(&vals[0])
200 };
201 match crate::kernel::net::ntp_query(&host) {
202 Ok(unix) => {
203 crate::kernel::timer::set_wall_clock(unix);
204 match crate::kernel::timer::format_datetime_jst() {
205 Some(dt) => Ok(Value::Str(alloc::format!(
206 "NTP synced via {}: {} JST",
207 host,
208 dt
209 ))),
210 None => Ok(Value::Str(alloc::format!(
211 "NTP synced via {}: unix={}",
212 host,
213 unix
214 ))),
215 }
216 }
217 Err(e) => Err(alloc::format!("NTP error: {}", e)),
218 }
219}
220
221pub(crate) fn builtin_date(env: &mut Env, args: &[AST]) -> Result<Value, String> {
222 let _ = eval_args(env, args)?;
223 match crate::kernel::timer::format_datetime_jst() {
224 Some(dt) => Ok(Value::Str(alloc::format!("{} JST", dt))),
225 None => Ok(Value::Str(String::from(
226 "Clock not synced. Run ntp() to sync via NTP.",
227 ))),
228 }
229}
230
231pub(crate) fn builtin_udp(env: &mut Env, args: &[AST]) -> Result<Value, String> {
232 let vals = eval_args(env, args)?;
233 if vals.is_empty() {
234 return Err(String::from(
235 "USAGE: udp(\"recv\" port) | udp(\"send\" \"ip\" port \"msg\")",
236 ));
237 }
238 let cmd = unquote(&vals[0]);
239
240 let parse_port = |s: &str| -> Option<u16> { s.parse::<u16>().ok() };
241
242 match cmd.as_str() {
243 "recv" => {
244 if vals.len() < 2 {
245 return Err(String::from("USAGE: udp(\"recv\" port)"));
246 }
247 let port_s = unquote(&vals[1]);
248 let port = parse_port(&port_s).unwrap_or(0);
249 if let Some(line) = crate::kernel::net::udp_recv_next_line(port) {
250 Ok(Value::Str(line))
251 } else {
252 Ok(Value::Str(alloc::format!(
253 "UDP RX queue empty on port {}",
254 port
255 )))
256 }
257 }
258 "send" => {
259 if vals.len() < 4 {
260 return Err(String::from("USAGE: udp(\"send\" \"ip\" port \"msg\") or udp(\"send\" \"ip\" port (bytes...))"));
261 }
262 let ip_s = unquote(&vals[1]);
263 let port_s = unquote(&vals[2]);
264 let dst_ip = crate::kernel::net::parse_ipv4(&ip_s)
265 .ok_or_else(|| alloc::format!("invalid IPv4 address: {}", ip_s))?;
266 let port = parse_port(&port_s).unwrap_or(0);
267
268 let payload = match &vals[3] {
269 Value::Str(s) => {
270 s.as_bytes().to_vec()
272 }
273 Value::List(lst) => {
274 let mut bytes = Vec::new();
276 for item in lst {
277 match item {
278 Value::Num(n) => {
279 let byte_val = n
280 .to_i64()
281 .ok_or_else(|| String::from("Number out of range for byte"))?;
282 if !(0..=255).contains(&byte_val) {
283 return Err(alloc::format!(
284 "Byte value {} out of range (0-255)",
285 byte_val
286 ));
287 }
288 bytes.push(byte_val as u8);
289 }
290 _ => return Err(String::from("List elements must be numbers (0-255)")),
291 }
292 }
293 bytes
294 }
295 _ => return Err(String::from("Message must be a string or list of numbers")),
296 };
297
298 let len = payload.len();
299 match crate::kernel::net::send_udp_ipv4(dst_ip, 49152, port, &payload) {
300 Ok(sent) => {
301 let display = match &vals[3] {
302 Value::Str(_) => unquote(&vals[3]),
303 Value::List(_) => alloc::format!("{} bytes", len),
304 _ => String::from(""),
305 };
306 Ok(Value::Str(alloc::format!(
307 "UDP TX: {} bytes -> {}:{} payload={}",
308 sent,
309 crate::kernel::net::format_ipv4(dst_ip),
310 port,
311 display
312 )))
313 }
314 Err(e) => Err(alloc::format!("UDP error: {}", e)),
315 }
316 }
317 "queue" => {
318 let port_filter = if vals.len() > 1 {
319 parse_port(&unquote(&vals[1]))
320 } else {
321 None
322 };
323 let lines = crate::kernel::net::udp_queue_lines(port_filter);
324 if lines.is_empty() {
325 Ok(Value::Str(String::from("UDP RX queue is empty")))
326 } else {
327 let mut out = String::from("UDP RX queue:\n");
328 for line in lines {
329 out.push_str(&alloc::format!(" - {}\n", line));
330 }
331 Ok(Value::Str(out))
332 }
333 }
334 _ => Err(String::from("Unknown udp command (inject command was removed)")),
335 }
336}
337
338pub(crate) fn builtin_tcp(env: &mut Env, args: &[AST]) -> Result<Value, String> {
339 let vals = eval_args(env, args)?;
340 let cmd = if vals.is_empty() {
341 String::from("show")
342 } else {
343 unquote(&vals[0])
344 };
345 let parse_port = |s: &str| -> Option<u16> { s.parse::<u16>().ok() };
346
347 match cmd.as_str() {
348 "connect" => {
349 if vals.len() < 3 {
350 return Err(String::from("USAGE: tcp(\"connect\" \"ip\" port)"));
351 }
352 let remote_ip_s = unquote(&vals[1]);
353 let remote_ip = crate::kernel::net::parse_ipv4(&remote_ip_s)
354 .ok_or_else(|| alloc::format!("invalid IPv4 address: {}", remote_ip_s))?;
355 let remote_port = parse_port(&unquote(&vals[2])).unwrap_or(0);
356 match crate::kernel::net::tcp_connect_real(remote_ip, remote_port) {
357 Ok(local_port) => Ok(Value::Str(alloc::format!(
358 "TCP connected: local {} -> {}:{}",
359 local_port,
360 crate::kernel::net::format_ipv4(remote_ip),
361 remote_port
362 ))),
363 Err(e) => Err(alloc::format!("TCP connect error: {}", e)),
364 }
365 }
366 "close" => {
367 if vals.len() < 2 {
368 return Err(String::from("USAGE: tcp(\"close\" local_port)"));
369 }
370 let local_port = parse_port(&unquote(&vals[1])).unwrap_or(0);
371 match crate::kernel::net::tcp_close_real(local_port) {
372 Ok(()) => Ok(Value::Str(alloc::format!(
373 "TCP closed: local {}",
374 local_port
375 ))),
376 Err(e) => Err(alloc::format!("TCP close error: {}", e)),
377 }
378 }
379 "show" => {
380 let lines = crate::kernel::net::tcp_socket_lines();
381 if lines.is_empty() {
382 Ok(Value::Str(String::from("TCP socket table is empty")))
383 } else {
384 let mut out = String::from("TCP sockets:\n");
385 for line in lines {
386 out.push_str(&alloc::format!(" - {}\n", line));
387 }
388 Ok(Value::Str(out))
389 }
390 }
391 _ => Err(String::from("Unknown tcp command")),
392 }
393}
394
395pub(crate) fn builtin_http(env: &mut Env, args: &[AST]) -> Result<Value, String> {
396 let vals = eval_args(env, args)?;
397 if vals.len() < 2 {
398 return Err(String::from(
399 "USAGE: http(\"get\"|\"post\" \"host\" [\"path\"] [\"content-type\" \"body\"])",
400 ));
401 }
402 let method = unquote(&vals[0]).to_lowercase();
403 let host = unquote(&vals[1]);
404 let path = if vals.len() > 2 {
405 unquote(&vals[2])
406 } else {
407 String::from("/")
408 };
409
410 if method == "post" {
411 let ct = if vals.len() > 3 {
412 unquote(&vals[3])
413 } else {
414 String::from("application/x-www-form-urlencoded")
415 };
416 let body_str = if vals.len() > 4 {
417 unquote(&vals[4])
418 } else {
419 String::new()
420 };
421 return match crate::kernel::net::http_post_real(&host, &path, &ct, body_str.as_bytes()) {
422 Ok(resp) => {
423 let body = if resp.body.len() > 96 {
424 alloc::format!("{}...", head_str(&resp.body, 96))
425 } else {
426 resp.body.clone()
427 };
428 Ok(Value::Str(alloc::format!(
429 "HTTP POST {}{} -> {} status={} {}\nHTTP body: {}",
430 host,
431 path,
432 crate::kernel::net::format_ipv4(resp.remote_ip),
433 resp.status_code,
434 resp.reason,
435 body
436 )))
437 }
438 Err(e) => Err(alloc::format!("HTTP POST error: {}", e)),
439 };
440 }
441
442 match crate::kernel::net::http_get_real(&host, &path) {
443 Ok(resp) => {
444 let body = if resp.body.len() > 96 {
445 alloc::format!("{}...", head_str(&resp.body, 96))
446 } else {
447 resp.body.clone()
448 };
449 Ok(Value::Str(alloc::format!(
450 "HTTP GET {}{} -> {} status={} {}\nHTTP body: {}",
451 host,
452 path,
453 crate::kernel::net::format_ipv4(resp.remote_ip),
454 resp.status_code,
455 resp.reason,
456 body
457 )))
458 }
459 Err(e) => Err(alloc::format!("HTTP error: {}", e)),
460 }
461}
462
463pub(crate) fn builtin_https(env: &mut Env, args: &[AST]) -> Result<Value, String> {
464 let vals = eval_args(env, args)?;
465 if vals.len() < 2 || unquote(&vals[0]) != "get" {
466 return Err(String::from("USAGE: https(\"get\" \"host\" [\"path\"])"));
467 }
468 let host = unquote(&vals[1]);
469 let path = if vals.len() > 2 {
470 unquote(&vals[2])
471 } else {
472 String::from("/")
473 };
474
475 match crate::kernel::net::https_get_real(&host, &path) {
476 Ok(resp) => {
477 let body = if resp.body.len() > 96 {
478 alloc::format!("{}...", head_str(&resp.body, 96))
479 } else {
480 resp.body.clone()
481 };
482 Ok(Value::Str(alloc::format!("HTTPS GET {}{} -> {} tls={} cipher={} status={} {}\nHTTPS cert: subject='{}'\nHTTPS body: {}", host, path, crate::kernel::net::format_ipv4(resp.remote_ip), resp.tls_version, resp.cipher_suite, resp.status_code, resp.reason, resp.cert_subject, body)))
483 }
484 Err(e) => Err(alloc::format!("HTTPS error: {}", e)),
485 }
486}
487
488