Skip to main content

strat9_kernel/shell/commands/net/
mod.rs

1//! Network commands (ping, ifconfig)
2mod ifconfig;
3mod netcmd;
4mod nslookup;
5mod ping;
6mod telnet;
7
8use crate::{
9    shell::ShellError,
10    shell_println,
11    vfs::{self, OpenFlags},
12};
13use alloc::string::String;
14pub use ifconfig::cmd_ifconfig;
15pub use netcmd::cmd_net;
16pub use nslookup::cmd_nslookup;
17pub use ping::cmd_ping;
18pub use telnet::cmd_telnet;
19
20/// Busy-wait for approximately `ms` milliseconds, yielding to other tasks.
21/// Scheduler ticks are 10ms each.
22pub(super) fn shell_sleep_ms(ms: u64) {
23    let ticks_to_wait = (ms + 9) / 10; // round up
24    let start = crate::process::scheduler::ticks();
25    loop {
26        crate::process::yield_task();
27        if crate::process::scheduler::ticks().wrapping_sub(start) >= ticks_to_wait {
28            break;
29        }
30    }
31}
32
33/// Performs the build ping path operation.
34fn build_ping_path<'a>(buf: &'a mut [u8; 80], target: &str) -> &'a str {
35    let prefix = b"/net/ping/";
36    let tlen = target.len().min(buf.len() - prefix.len());
37    buf[..prefix.len()].copy_from_slice(prefix);
38    buf[prefix.len()..prefix.len() + tlen].copy_from_slice(&target.as_bytes()[..tlen]);
39    let total = prefix.len() + tlen;
40    core::str::from_utf8(&buf[..total]).unwrap_or("/net/ping/0.0.0.0")
41}
42
43/// Performs the cmd ping operation.
44pub(super) fn cmd_ping_impl(args: &[String]) -> Result<(), ShellError> {
45    let target = if args.is_empty() {
46        match vfs::open("/net/gateway", OpenFlags::READ) {
47            Ok(fd) => {
48                let mut buf = [0u8; 64];
49                let n = vfs::read(fd, &mut buf).unwrap_or(0);
50                let _ = vfs::close(fd);
51                let s = core::str::from_utf8(&buf[..n]).unwrap_or("").trim();
52                if s.is_empty() || s.starts_with("0.0.0.0") {
53                    shell_println!("No gateway available. Usage: ping <ip>");
54                    return Ok(());
55                }
56                String::from(s)
57            }
58            Err(_) => {
59                shell_println!("/net not available. Is strate-net running?");
60                return Ok(());
61            }
62        }
63    } else {
64        args[0].clone()
65    };
66
67    let count: u32 = if args.len() > 1 {
68        args[1].parse().unwrap_or(4)
69    } else {
70        4
71    };
72
73    shell_println!("PING {} ({} packets)", target, count);
74
75    let mut path_buf = [0u8; 80];
76    let path = build_ping_path(&mut path_buf, &target);
77
78    let mut sent: u32 = 0;
79    let mut received: u32 = 0;
80
81    for seq in 0..count {
82        if crate::shell::is_interrupted() {
83            shell_println!("^C");
84            break;
85        }
86        match vfs::open(path, OpenFlags::WRITE) {
87            Ok(fd) => {
88                let mut req = [0u8; 4];
89                req[0..2].copy_from_slice(&(seq as u16).to_le_bytes());
90                let _ = vfs::write(fd, &req);
91                let _ = vfs::close(fd);
92                sent += 1;
93            }
94            Err(_) => {
95                shell_println!("  send failed (seq={})", seq);
96                sent += 1;
97                continue;
98            }
99        }
100
101        let mut got_reply = false;
102        for _ in 0..20 {
103            shell_sleep_ms(50);
104
105            match vfs::open(path, OpenFlags::READ) {
106                Ok(fd) => {
107                    let mut reply_buf = [0u8; 10];
108                    match vfs::read(fd, &mut reply_buf) {
109                        Ok(n) if n >= 10 => {
110                            let _ = vfs::close(fd);
111                            let rtt_us = u64::from_le_bytes([
112                                reply_buf[2],
113                                reply_buf[3],
114                                reply_buf[4],
115                                reply_buf[5],
116                                reply_buf[6],
117                                reply_buf[7],
118                                reply_buf[8],
119                                reply_buf[9],
120                            ]);
121                            let rtt_ms = rtt_us / 1000;
122                            let rtt_frac = (rtt_us % 1000) / 100;
123                            shell_println!(
124                                "  Reply from {}: seq={} time={}.{}ms",
125                                target,
126                                seq,
127                                rtt_ms,
128                                rtt_frac
129                            );
130                            received += 1;
131                            got_reply = true;
132                            break;
133                        }
134                        _ => {
135                            let _ = vfs::close(fd);
136                        }
137                    }
138                }
139                Err(_) => break,
140            }
141        }
142
143        if !got_reply {
144            shell_println!("  Request timeout: seq={}", seq);
145        }
146    }
147
148    let loss = if sent > 0 {
149        ((sent - received) * 100) / sent
150    } else {
151        100
152    };
153    shell_println!("--- {} ping statistics ---", target);
154    shell_println!(
155        "{} transmitted, {} received, {}% loss",
156        sent,
157        received,
158        loss
159    );
160
161    Ok(())
162}
163
164/// Performs the cmd ifconfig operation.
165pub(super) fn cmd_ifconfig_impl(args: &[String]) -> Result<(), ShellError> {
166    if !args.is_empty() {
167        match args[0].as_str() {
168            "inet" => {
169                if args.len() != 2 {
170                    shell_println!("Usage: ifconfig inet <ipv4/prefix>");
171                    return Err(ShellError::InvalidArguments);
172                }
173                let path = alloc::format!("/net/ip/set/{}", args[1]);
174                write_path(&path, b"1")?;
175                shell_println!("ifconfig: inet set to {}", args[1]);
176                return Ok(());
177            }
178            "gateway" => {
179                if args.len() == 2 && args[1].as_str() == "clear" {
180                    write_path("/net/route/default/clear", b"1")?;
181                    shell_println!("ifconfig: default gateway cleared");
182                    return Ok(());
183                }
184                if args.len() != 2 {
185                    shell_println!("Usage: ifconfig gateway <ipv4|clear>");
186                    return Err(ShellError::InvalidArguments);
187                }
188                let path = alloc::format!("/net/route/default/set/{}", args[1]);
189                write_path(&path, b"1")?;
190                shell_println!("ifconfig: default gateway set to {}", args[1]);
191                return Ok(());
192            }
193            "dns" => {
194                if args.len() < 2 || args.len() > 4 {
195                    shell_println!("Usage: ifconfig dns <ipv4> [ipv4] [ipv4]");
196                    shell_println!("       ifconfig dns clear");
197                    return Err(ShellError::InvalidArguments);
198                }
199                if args.len() == 2 && args[1].as_str() == "clear" {
200                    write_path("/net/dns/set/0/0.0.0.0", b"1")?;
201                    write_path("/net/dns/set/1/0.0.0.0", b"1")?;
202                    write_path("/net/dns/set/2/0.0.0.0", b"1")?;
203                    shell_println!("ifconfig: DNS cleared");
204                    return Ok(());
205                }
206                write_path("/net/dns/set/0/0.0.0.0", b"1")?;
207                write_path("/net/dns/set/1/0.0.0.0", b"1")?;
208                write_path("/net/dns/set/2/0.0.0.0", b"1")?;
209                for (idx, ip) in args[1..].iter().enumerate() {
210                    let path = alloc::format!("/net/dns/set/{}/{}", idx, ip);
211                    write_path(&path, b"1")?;
212                }
213                shell_println!("ifconfig: DNS updated");
214                return Ok(());
215            }
216            "dhcp" => {
217                if args.len() != 2 {
218                    shell_println!("Usage: ifconfig dhcp <on|off>");
219                    return Err(ShellError::InvalidArguments);
220                }
221                match args[1].as_str() {
222                    "on" => {
223                        write_path("/net/dhcp/enable", b"1")?;
224                        shell_println!("ifconfig: DHCP enabled");
225                        return Ok(());
226                    }
227                    "off" => {
228                        write_path("/net/dhcp/disable", b"1")?;
229                        shell_println!("ifconfig: DHCP disabled");
230                        return Ok(());
231                    }
232                    _ => {
233                        shell_println!("Usage: ifconfig dhcp <on|off>");
234                        return Err(ShellError::InvalidArguments);
235                    }
236                }
237            }
238            _ => {
239                shell_println!("Usage: ifconfig");
240                shell_println!("       ifconfig inet <ipv4/prefix>");
241                shell_println!("       ifconfig gateway <ipv4|clear>");
242                shell_println!("       ifconfig dns <ipv4> [ipv4] [ipv4]");
243                shell_println!("       ifconfig dns clear");
244                shell_println!("       ifconfig dhcp <on|off>");
245                return Err(ShellError::InvalidArguments);
246            }
247        }
248    }
249
250    let read_file = |path: &str| -> String {
251        match vfs::open(path, OpenFlags::READ) {
252            Ok(fd) => {
253                let mut buf = [0u8; 96];
254                let n = vfs::read(fd, &mut buf).unwrap_or(0);
255                let _ = vfs::close(fd);
256                let s = core::str::from_utf8(&buf[..n]).unwrap_or("").trim();
257                String::from(s)
258            }
259            Err(_) => String::from("(unavailable)"),
260        }
261    };
262
263    let ip = read_file("/net/ip");
264    let gw = read_file("/net/gateway");
265    let route = read_file("/net/route");
266    let routes = read_file("/net/routes");
267    let dns = read_file("/net/dns");
268    let dhcp = read_file("/net/dhcp");
269
270    shell_println!("em0:");
271    shell_println!("  inet     {}", ip);
272    shell_println!("  dhcp     {}", dhcp);
273    shell_println!("  gateway  {}", gw);
274    shell_println!("  route    {}", route);
275    shell_println!("  routes   {}", routes);
276    shell_println!("  dns      {}", dns);
277
278    Ok(())
279}
280
281/// Writes path.
282fn write_path(path: &str, data: &[u8]) -> Result<(), ShellError> {
283    let fd = vfs::open(path, OpenFlags::WRITE).map_err(|_| ShellError::ExecutionFailed)?;
284    let res = vfs::write(fd, data).map(|_| ());
285    let _ = vfs::close(fd);
286    res.map_err(|_| ShellError::ExecutionFailed)
287}
288
289/// Performs the cmd net operation.
290pub(super) fn cmd_net_impl(args: &[String]) -> Result<(), ShellError> {
291    if args.is_empty() {
292        shell_println!("Usage: net route <show|add|del|default> ...");
293        return Err(ShellError::InvalidArguments);
294    }
295
296    match args[0].as_str() {
297        "route" => {
298            if args.len() < 2 {
299                shell_println!("Usage: net route <show|add|del|default> ...");
300                return Err(ShellError::InvalidArguments);
301            }
302
303            match args[1].as_str() {
304                "show" => {
305                    let routes = match vfs::open("/net/routes", OpenFlags::READ) {
306                        Ok(fd) => {
307                            let mut buf = [0u8; 256];
308                            let n = vfs::read(fd, &mut buf).unwrap_or(0);
309                            let _ = vfs::close(fd);
310                            String::from(core::str::from_utf8(&buf[..n]).unwrap_or("").trim())
311                        }
312                        Err(_) => String::from("(unavailable)"),
313                    };
314                    shell_println!("routes:");
315                    shell_println!("{}", routes);
316                    Ok(())
317                }
318                "add" => {
319                    if args.len() != 4 {
320                        shell_println!("Usage: net route add <cidr> <gateway>");
321                        return Err(ShellError::InvalidArguments);
322                    }
323                    let path = alloc::format!("/net/route/add/{}/{}", args[2], args[3]);
324                    write_path(&path, b"1")?;
325                    shell_println!("net route add: ok ({} via {})", args[2], args[3]);
326                    Ok(())
327                }
328                "del" => {
329                    if args.len() != 3 {
330                        shell_println!("Usage: net route del <cidr>");
331                        return Err(ShellError::InvalidArguments);
332                    }
333                    let path = alloc::format!("/net/route/del/{}", args[2]);
334                    write_path(&path, b"1")?;
335                    shell_println!("net route del: ok ({})", args[2]);
336                    Ok(())
337                }
338                "default" => {
339                    if args.len() == 4 && args[2].as_str() == "set" {
340                        let path = alloc::format!("/net/route/default/set/{}", args[3]);
341                        write_path(&path, b"1")?;
342                        shell_println!("net route default: ok (via {})", args[3]);
343                        return Ok(());
344                    }
345                    if args.len() == 3 && args[2].as_str() == "clear" {
346                        write_path("/net/route/default/clear", b"1")?;
347                        shell_println!("net route default: cleared");
348                        return Ok(());
349                    }
350                    shell_println!("Usage: net route default <set <gateway>|clear>");
351                    Err(ShellError::InvalidArguments)
352                }
353                _ => {
354                    shell_println!("Usage: net route <show|add|del|default> ...");
355                    Err(ShellError::InvalidArguments)
356                }
357            }
358        }
359        _ => {
360            shell_println!("Usage: net route <show|add|del|default> ...");
361            Err(ShellError::InvalidArguments)
362        }
363    }
364}