Skip to main content

strat9_kernel/shell/commands/net/
mod.rs

1//! Network commands (ping, ifconfig)
2mod ifconfig;
3mod netcmd;
4mod nslookup;
5mod ping_userspace;
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_userspace::cmd_ping;
18use strat9_abi::ip::{is_ipv4_literal_candidate, parse_ipv4_literal};
19pub use telnet::cmd_telnet;
20
21/// Read an ELF binary from a VFS path into a Vec<u8>.
22/// Shared helper used by ping and other commands that spawn userspace binaries.
23pub(super) fn read_elf(path: &str) -> Result<alloc::vec::Vec<u8>, ShellError> {
24    let fd = vfs::open(path, OpenFlags::READ).map_err(|_| {
25        shell_println!("{}: not found", path);
26        ShellError::ExecutionFailed
27    })?;
28    let data = vfs::read_all(fd).map_err(|_| {
29        let _ = vfs::close(fd);
30        shell_println!("{}: read error", path);
31        ShellError::ExecutionFailed
32    })?;
33    let _ = vfs::close(fd);
34    Ok(data)
35}
36
37/// Spawn a userspace ELF binary from `/initfs/bin/<name>` with the given
38/// shell arguments as `argv[1..]`.  Returns immediately after scheduling.
39pub(super) fn spawn_elf_with_args(
40    bin_path: &str,
41    task_name: &'static str,
42    args: &[String],
43) -> Result<(), ShellError> {
44    let data = read_elf(bin_path)?;
45    let arg_strs: alloc::vec::Vec<&str> = args.iter().map(|s| s.as_str()).collect();
46    match crate::process::elf::load_and_run_elf_with_args(&data, task_name, &arg_strs) {
47        Ok(task_id) => {
48            shell_println!("{}: started (task={})", task_name, task_id);
49            Ok(())
50        }
51        Err(e) => {
52            shell_println!("{}: failed to launch: {}", task_name, e);
53            Err(ShellError::ExecutionFailed)
54        }
55    }
56}
57
58/// Performs the cmd ping spawn operation (launches /initfs/bin/ping with args).
59pub(super) fn cmd_ping_spawn_impl(args: &[String]) -> Result<(), ShellError> {
60    // Early IPv4 validation : same behaviour as kping
61    if let Some(target) = args.first() {
62        if is_ipv4_literal_candidate(target) && parse_ipv4_literal(target).is_none() {
63            shell_println!("ping: invalid IPv4 address: {}", target);
64            return Ok(());
65        }
66    }
67    spawn_elf_with_args("/initfs/bin/ping", "ping", args)
68}
69
70/// Busy-wait for approximately `ms` milliseconds, yielding to other tasks.
71/// Scheduler ticks are 10ms each.
72pub(super) fn shell_sleep_ms(ms: u64) {
73    let ticks_to_wait = (ms + 9) / 10; // round up
74    let start = crate::process::scheduler::ticks();
75    loop {
76        crate::process::yield_task();
77        if crate::process::scheduler::ticks().wrapping_sub(start) >= ticks_to_wait {
78            break;
79        }
80    }
81}
82
83/// Performs the cmd ifconfig operation.
84pub(super) fn cmd_ifconfig_impl(args: &[String]) -> Result<(), ShellError> {
85    if !args.is_empty() {
86        match args[0].as_str() {
87            "inet" => {
88                if args.len() != 2 {
89                    shell_println!("Usage: ifconfig inet <ipv4/prefix>");
90                    return Err(ShellError::InvalidArguments);
91                }
92                let path = alloc::format!("/net/ip/set/{}", args[1]);
93                write_path(&path, b"1")?;
94                shell_println!("ifconfig: inet set to {}", args[1]);
95                return Ok(());
96            }
97            "gateway" => {
98                if args.len() == 2 && args[1].as_str() == "clear" {
99                    write_path("/net/route/default/clear", b"1")?;
100                    shell_println!("ifconfig: default gateway cleared");
101                    return Ok(());
102                }
103                if args.len() != 2 {
104                    shell_println!("Usage: ifconfig gateway <ipv4|clear>");
105                    return Err(ShellError::InvalidArguments);
106                }
107                let path = alloc::format!("/net/route/default/set/{}", args[1]);
108                write_path(&path, b"1")?;
109                shell_println!("ifconfig: default gateway set to {}", args[1]);
110                return Ok(());
111            }
112            "dns" => {
113                if args.len() < 2 || args.len() > 4 {
114                    shell_println!("Usage: ifconfig dns <ipv4> [ipv4] [ipv4]");
115                    shell_println!("       ifconfig dns clear");
116                    return Err(ShellError::InvalidArguments);
117                }
118                if args.len() == 2 && args[1].as_str() == "clear" {
119                    write_path("/net/dns/set/0/0.0.0.0", b"1")?;
120                    write_path("/net/dns/set/1/0.0.0.0", b"1")?;
121                    write_path("/net/dns/set/2/0.0.0.0", b"1")?;
122                    shell_println!("ifconfig: DNS cleared");
123                    return Ok(());
124                }
125                write_path("/net/dns/set/0/0.0.0.0", b"1")?;
126                write_path("/net/dns/set/1/0.0.0.0", b"1")?;
127                write_path("/net/dns/set/2/0.0.0.0", b"1")?;
128                for (idx, ip) in args[1..].iter().enumerate() {
129                    let path = alloc::format!("/net/dns/set/{}/{}", idx, ip);
130                    write_path(&path, b"1")?;
131                }
132                shell_println!("ifconfig: DNS updated");
133                return Ok(());
134            }
135            "dhcp" => {
136                if args.len() != 2 {
137                    shell_println!("Usage: ifconfig dhcp <on|off>");
138                    return Err(ShellError::InvalidArguments);
139                }
140                match args[1].as_str() {
141                    "on" => {
142                        write_path("/net/dhcp/enable", b"1")?;
143                        shell_println!("ifconfig: DHCP enabled");
144                        return Ok(());
145                    }
146                    "off" => {
147                        write_path("/net/dhcp/disable", b"1")?;
148                        shell_println!("ifconfig: DHCP disabled");
149                        return Ok(());
150                    }
151                    _ => {
152                        shell_println!("Usage: ifconfig dhcp <on|off>");
153                        return Err(ShellError::InvalidArguments);
154                    }
155                }
156            }
157            _ => {
158                shell_println!("Usage: ifconfig");
159                shell_println!("       ifconfig inet <ipv4/prefix>");
160                shell_println!("       ifconfig gateway <ipv4|clear>");
161                shell_println!("       ifconfig dns <ipv4> [ipv4] [ipv4]");
162                shell_println!("       ifconfig dns clear");
163                shell_println!("       ifconfig dhcp <on|off>");
164                return Err(ShellError::InvalidArguments);
165            }
166        }
167    }
168
169    let read_file = |path: &str| -> String {
170        match vfs::open(path, OpenFlags::READ) {
171            Ok(fd) => {
172                let mut buf = [0u8; 96];
173                let n = vfs::read(fd, &mut buf).unwrap_or(0);
174                let _ = vfs::close(fd);
175                let s = core::str::from_utf8(&buf[..n]).unwrap_or("").trim();
176                String::from(s)
177            }
178            Err(_) => String::from("(unavailable)"),
179        }
180    };
181
182    let ip = read_file("/net/ip");
183    let ip6 = read_file("/net/ip6");
184    let gw = read_file("/net/gateway");
185    let gw6 = read_file("/net/ip6/gateway");
186    let route = read_file("/net/route");
187    let routes = read_file("/net/routes");
188    let dns = read_file("/net/dns");
189    let dhcp = read_file("/net/dhcp");
190
191    shell_println!("em0:");
192    shell_println!("  inet     {}", ip);
193    shell_println!("  inet6    {}", ip6);
194    shell_println!("  dhcp     {}", dhcp);
195    shell_println!("  gateway  {}", gw);
196    shell_println!("  gateway6 {}", gw6);
197    shell_println!("  route    {}", route);
198    shell_println!("  routes   {}", routes);
199    shell_println!("  dns      {}", dns);
200
201    Ok(())
202}
203
204/// Writes path.
205fn write_path(path: &str, data: &[u8]) -> Result<(), ShellError> {
206    let fd = vfs::open(path, OpenFlags::WRITE).map_err(|_| ShellError::ExecutionFailed)?;
207    let res = vfs::write(fd, data).map(|_| ());
208    let _ = vfs::close(fd);
209    res.map_err(|_| ShellError::ExecutionFailed)
210}
211
212/// Performs the cmd net operation.
213pub(super) fn cmd_net_impl(args: &[String]) -> Result<(), ShellError> {
214    if args.is_empty() {
215        shell_println!("Usage: net route <show|add|del|default> ...");
216        return Err(ShellError::InvalidArguments);
217    }
218
219    match args[0].as_str() {
220        "route" => {
221            if args.len() < 2 {
222                shell_println!("Usage: net route <show|add|del|default> ...");
223                return Err(ShellError::InvalidArguments);
224            }
225
226            match args[1].as_str() {
227                "show" => {
228                    let routes = match vfs::open("/net/routes", OpenFlags::READ) {
229                        Ok(fd) => {
230                            let mut buf = [0u8; 256];
231                            let n = vfs::read(fd, &mut buf).unwrap_or(0);
232                            let _ = vfs::close(fd);
233                            String::from(core::str::from_utf8(&buf[..n]).unwrap_or("").trim())
234                        }
235                        Err(_) => String::from("(unavailable)"),
236                    };
237                    shell_println!("routes:");
238                    shell_println!("{}", routes);
239                    Ok(())
240                }
241                "add" => {
242                    if args.len() != 4 {
243                        shell_println!("Usage: net route add <cidr> <gateway>");
244                        return Err(ShellError::InvalidArguments);
245                    }
246                    let path = alloc::format!("/net/route/add/{}/{}", args[2], args[3]);
247                    write_path(&path, b"1")?;
248                    shell_println!("net route add: ok ({} via {})", args[2], args[3]);
249                    Ok(())
250                }
251                "del" => {
252                    if args.len() != 3 {
253                        shell_println!("Usage: net route del <cidr>");
254                        return Err(ShellError::InvalidArguments);
255                    }
256                    let path = alloc::format!("/net/route/del/{}", args[2]);
257                    write_path(&path, b"1")?;
258                    shell_println!("net route del: ok ({})", args[2]);
259                    Ok(())
260                }
261                "default" => {
262                    if args.len() == 4 && args[2].as_str() == "set" {
263                        let path = alloc::format!("/net/route/default/set/{}", args[3]);
264                        write_path(&path, b"1")?;
265                        shell_println!("net route default: ok (via {})", args[3]);
266                        return Ok(());
267                    }
268                    if args.len() == 3 && args[2].as_str() == "clear" {
269                        write_path("/net/route/default/clear", b"1")?;
270                        shell_println!("net route default: cleared");
271                        return Ok(());
272                    }
273                    shell_println!("Usage: net route default <set <gateway>|clear>");
274                    Err(ShellError::InvalidArguments)
275                }
276                _ => {
277                    shell_println!("Usage: net route <show|add|del|default> ...");
278                    Err(ShellError::InvalidArguments)
279                }
280            }
281        }
282        _ => {
283            shell_println!("Usage: net route <show|add|del|default> ...");
284            Err(ShellError::InvalidArguments)
285        }
286    }
287}