mirror of https://github.com/vinc/moros.git
Add socket command (#341)
* Add socket command * Add test.html page * Refactory sending code * Update documentation * Fix newlines * Add interval option * Replace newlines * Exit on end of transmission * Add listen mode * Fix arguments parsing * Remove debug output
This commit is contained in:
parent
8d2448281d
commit
530658221d
|
@ -49,7 +49,7 @@ Listen for packets transmitted on the network:
|
|||
|
||||
The `dhcp` command configures the network automatically:
|
||||
|
||||
> dhcp -v
|
||||
> dhcp --verbose
|
||||
DEBUG: DHCP Discover transmitted
|
||||
DEBUG: DHCP Offer received
|
||||
ip: 10.0.2.15/24
|
||||
|
@ -68,15 +68,14 @@ The `host` command performs DNS lookups:
|
|||
|
||||
The `tcp` command connects to TCP sockets:
|
||||
|
||||
> tcp time.nist.gov 13
|
||||
Connecting to 129.6.15.30:13
|
||||
> tcp time.nist.gov 13 --verbose
|
||||
DEBUG: Connecting to 129.6.15.30:13
|
||||
|
||||
58884 20-02-05 19:19:42 00 0 0 49.2 UTC(NIST) *
|
||||
|
||||
This could also be done with the `read` command:
|
||||
|
||||
> read /net/tcp/time.nist.gov:13
|
||||
Connecting to 129.6.15.30:13
|
||||
|
||||
58884 20-02-05 19:19:55 00 0 0 49.2 UTC(NIST) *
|
||||
|
||||
|
@ -85,12 +84,79 @@ This could also be done with the `read` command:
|
|||
|
||||
Requesting a resource on a host:
|
||||
|
||||
> http example.com /articles/index.html
|
||||
> http moros.cc /test.html
|
||||
|
||||
Is equivalent to:
|
||||
|
||||
> read /net/http/example.com/articles
|
||||
> read /net/http/moros.cc/test.html
|
||||
|
||||
And:
|
||||
|
||||
> read /net/http/example.com:80/articles/index.html
|
||||
> read /net/http/moros.cc:80/test.html
|
||||
|
||||
## SOCKET
|
||||
|
||||
The `socket` command is used to read and write to network connexions
|
||||
like the `netcat` command on Unix.
|
||||
|
||||
For example the request made with `tcp` above is equivalent to this:
|
||||
|
||||
> socket time.nist.gov 13 --read-only
|
||||
|
||||
59710 22-05-11 21:44:52 50 0 0 359.3 UTC(NIST) *
|
||||
|
||||
And the request made with `http` is equivalent to that:
|
||||
|
||||
> socket moros.cc 80 --prompt
|
||||
MOROS Socket v0.1.0
|
||||
|
||||
> GET /test.html HTTP/1.0
|
||||
> Host: moros.cc
|
||||
>
|
||||
HTTP/1.1 200 OK
|
||||
Server: nginx
|
||||
Date: Wed, 11 May 2022 21:46:34 GMT
|
||||
Content-Type: text/html
|
||||
Content-Length: 866
|
||||
Connection: close
|
||||
Last-Modified: Fri, 29 Oct 2021 17:50:58 GMT
|
||||
ETag: "617c3482-362"
|
||||
Accept-Ranges: bytes
|
||||
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>MOROS: Obscure Rust Operating System</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>MOROS</h1>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Here's a connexion to a SMTP server to send a mail:
|
||||
|
||||
> socket 10.0.2.2 2500 --prompt
|
||||
MOROS Socket v0.1.0
|
||||
|
||||
220 EventMachine SMTP Server
|
||||
> EHLO moros.cc
|
||||
250-Ok EventMachine SMTP Server
|
||||
250-NO-SOLICITING
|
||||
250 SIZE 20000000
|
||||
> MAIL FROM:<v@moros.cc>
|
||||
> RCPT TO:<alice@example.com>
|
||||
250 Ok
|
||||
250 Ok
|
||||
> DATA
|
||||
354 Send it
|
||||
> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nec
|
||||
> diam vitae ex blandit malesuada nec a turpis.
|
||||
> .
|
||||
> QUIT
|
||||
250 Message accepted
|
||||
221 Ok
|
||||
|
||||
Sending a file to a server:
|
||||
|
||||
> socket 10.0.2.2 1234 <= /tmp/alice.txt
|
||||
|
|
|
@ -86,6 +86,7 @@ impl FileIO for Device {
|
|||
Device::Null => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, ()> {
|
||||
match self {
|
||||
Device::File(io) => io.write(buf),
|
||||
|
|
|
@ -32,7 +32,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
|||
iface.remove_socket(dhcp_handle);
|
||||
return usr::shell::ExitCode::CommandError;
|
||||
}
|
||||
if sys::console::end_of_text() {
|
||||
if sys::console::end_of_text() || sys::console::end_of_transmission() {
|
||||
eprintln!();
|
||||
iface.remove_socket(dhcp_handle);
|
||||
return usr::shell::ExitCode::CommandError;
|
||||
|
|
|
@ -60,7 +60,7 @@ fn erase(pathname: &str) -> usr::shell::ExitCode {
|
|||
let buf = vec![0; drive.block_size() as usize];
|
||||
print!("\x1b[?25l"); // Disable cursor
|
||||
for i in 0..n {
|
||||
if sys::console::end_of_text() {
|
||||
if sys::console::end_of_text() || sys::console::end_of_transmission() {
|
||||
println!();
|
||||
print!("\x1b[?25h"); // Enable cursor
|
||||
return usr::shell::ExitCode::CommandError;
|
||||
|
|
|
@ -120,8 +120,8 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
|||
iface.remove_socket(tcp_handle);
|
||||
return usr::shell::ExitCode::CommandError;
|
||||
}
|
||||
if sys::console::end_of_text() {
|
||||
eprintln!(); // FIXME
|
||||
if sys::console::end_of_text() || sys::console::end_of_transmission() {
|
||||
eprintln!();
|
||||
iface.remove_socket(tcp_handle);
|
||||
return usr::shell::ExitCode::CommandError;
|
||||
}
|
||||
|
@ -220,6 +220,6 @@ fn help() -> usr::shell::ExitCode {
|
|||
println!("{}Usage:{} http {}<options> <url>{1}", csi_title, csi_reset, csi_option);
|
||||
println!();
|
||||
println!("{}Options:{}", csi_title, csi_reset);
|
||||
println!(" {0}--verbose{1} Increase verbosity", csi_option, csi_reset);
|
||||
println!(" {0}-v{1}, {0}--verbose{1} Increase verbosity", csi_option, csi_reset);
|
||||
usr::shell::ExitCode::CommandSuccessful
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ pub fn main(_args: &[&str]) -> usr::shell::ExitCode {
|
|||
|
||||
let mut send_queue: VecDeque<Vec<u8>> = VecDeque::new();
|
||||
loop {
|
||||
if sys::console::end_of_text() {
|
||||
if sys::console::end_of_text() || sys::console::end_of_transmission() {
|
||||
iface.remove_socket(tcp_handle);
|
||||
println!();
|
||||
return usr::shell::ExitCode::CommandSuccessful;
|
||||
|
|
|
@ -34,6 +34,7 @@ pub mod read;
|
|||
pub mod reboot;
|
||||
pub mod shell;
|
||||
pub mod sleep;
|
||||
pub mod socket;
|
||||
pub mod tcp;
|
||||
pub mod uptime;
|
||||
pub mod user;
|
||||
|
|
|
@ -61,8 +61,9 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
|||
let tcp_handle = iface.add_socket(tcp_socket);
|
||||
|
||||
loop {
|
||||
if sys::console::end_of_text() {
|
||||
if sys::console::end_of_text() || sys::console::end_of_transmission() {
|
||||
println!();
|
||||
iface.remove_socket(tcp_handle);
|
||||
return usr::shell::ExitCode::CommandSuccessful;
|
||||
}
|
||||
syscall::sleep(0.1);
|
||||
|
|
|
@ -86,7 +86,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
|||
} else if info.is_device() {
|
||||
let is_console = info.size() == 4; // TODO: Improve device detection
|
||||
loop {
|
||||
if sys::console::end_of_text() {
|
||||
if sys::console::end_of_text() || sys::console::end_of_transmission() {
|
||||
println!();
|
||||
return usr::shell::ExitCode::CommandSuccessful;
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@ use alloc::vec::Vec;
|
|||
use alloc::string::String;
|
||||
|
||||
// TODO: Scan /bin
|
||||
const AUTOCOMPLETE_COMMANDS: [&str; 38] = [
|
||||
const AUTOCOMPLETE_COMMANDS: [&str; 39] = [
|
||||
"2048", "base64", "calc", "clear", "colors", "copy", "date", "delete", "dhcp", "disk", "edit",
|
||||
"env", "exit", "geotime", "goto", "halt", "help", "hex", "host", "http", "httpd", "install",
|
||||
"keyboard", "lisp", "list", "memory", "move", "net", "pci", "print", "read", "reboot", "shell",
|
||||
"sleep", "tcp", "user", "vga", "write"
|
||||
"sleep", "socket", "tcp", "user", "vga", "write"
|
||||
];
|
||||
|
||||
#[repr(u8)]
|
||||
|
@ -254,6 +254,7 @@ pub fn exec(cmd: &str) -> ExitCode {
|
|||
"dhcp" => usr::dhcp::main(&args),
|
||||
"http" => usr::http::main(&args),
|
||||
"httpd" => usr::httpd::main(&args),
|
||||
"socket" => usr::socket::main(&args),
|
||||
"tcp" => usr::tcp::main(&args),
|
||||
"host" => usr::host::main(&args),
|
||||
"install" => usr::install::main(&args),
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
use crate::{sys, usr, debug};
|
||||
use crate::api::console::Style;
|
||||
use crate::api::io;
|
||||
use crate::api::syscall;
|
||||
use crate::api::random;
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use core::str::{self, FromStr};
|
||||
use smoltcp::socket::{TcpSocket, TcpSocketBuffer, TcpState};
|
||||
use smoltcp::time::Instant;
|
||||
use smoltcp::wire::IpAddress;
|
||||
|
||||
pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
||||
let mut listen = false;
|
||||
let mut prompt = false;
|
||||
let mut verbose = false;
|
||||
let mut read_only = false;
|
||||
let mut interval = 0.0;
|
||||
let mut next_arg_is_interval = false;
|
||||
let mut args: Vec<&str> = args.iter().filter_map(|arg| {
|
||||
match *arg {
|
||||
"-l" | "--listen" => {
|
||||
listen = true;
|
||||
None
|
||||
}
|
||||
"-p" | "--prompt" => {
|
||||
prompt = true;
|
||||
None
|
||||
}
|
||||
"-r" | "--read-only" => {
|
||||
read_only = true;
|
||||
None
|
||||
}
|
||||
"-v" | "--verbose" => {
|
||||
verbose = true;
|
||||
None
|
||||
}
|
||||
"-i" | "--interval" => {
|
||||
next_arg_is_interval = true;
|
||||
None
|
||||
}
|
||||
_ if next_arg_is_interval => {
|
||||
next_arg_is_interval = false;
|
||||
if let Ok(i) = arg.parse() {
|
||||
interval = i;
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => {
|
||||
Some(*arg)
|
||||
}
|
||||
}
|
||||
}).collect();
|
||||
if prompt {
|
||||
println!("MOROS Socket v0.1.0\n");
|
||||
}
|
||||
|
||||
let required_args_count = if listen { 2 } else { 3 };
|
||||
|
||||
if args.len() == required_args_count - 1 {
|
||||
if let Some(i) = args[1].find(':') { // Split <host> and <port>
|
||||
let arg = args[1].clone();
|
||||
let (host, path) = arg.split_at(i);
|
||||
args[1] = host;
|
||||
args.push(&path[1..]);
|
||||
}
|
||||
}
|
||||
|
||||
if args.len() != required_args_count {
|
||||
help();
|
||||
return usr::shell::ExitCode::CommandError;
|
||||
}
|
||||
|
||||
let host = if listen { "0.0.0.0" } else { &args[1] };
|
||||
let port: u16 = args[required_args_count - 1].parse().expect("Could not parse port");
|
||||
|
||||
let address = if host.ends_with(char::is_numeric) {
|
||||
IpAddress::from_str(host).expect("invalid address format")
|
||||
} else {
|
||||
match usr::host::resolve(host) {
|
||||
Ok(ip_addr) => {
|
||||
ip_addr
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Could not resolve host: {:?}", e);
|
||||
return usr::shell::ExitCode::CommandError;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let tcp_rx_buffer = TcpSocketBuffer::new(vec![0; 1024]);
|
||||
let tcp_tx_buffer = TcpSocketBuffer::new(vec![0; 1024]);
|
||||
let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
|
||||
|
||||
#[derive(Debug)]
|
||||
enum State { Connecting, Sending, Receiving }
|
||||
let mut state = State::Connecting;
|
||||
|
||||
if let Some(ref mut iface) = *sys::net::IFACE.lock() {
|
||||
let tcp_handle = iface.add_socket(tcp_socket);
|
||||
|
||||
loop {
|
||||
if sys::console::end_of_text() || sys::console::end_of_transmission() {
|
||||
eprintln!();
|
||||
iface.remove_socket(tcp_handle);
|
||||
return usr::shell::ExitCode::CommandError;
|
||||
}
|
||||
let timestamp = Instant::from_micros((syscall::realtime() * 1000000.0) as i64);
|
||||
if let Err(e) = iface.poll(timestamp) {
|
||||
error!("Network Error: {}", e);
|
||||
}
|
||||
|
||||
let (socket, cx) = iface.get_socket_and_context::<TcpSocket>(tcp_handle);
|
||||
if verbose {
|
||||
debug!("*********************************");
|
||||
debug!("APP State: {:?}", state);
|
||||
debug!("TCP State: {:?}", socket.state());
|
||||
debug!("is active: {}", socket.is_active());
|
||||
debug!("is open: {}", socket.is_open());
|
||||
debug!("can recv: {}", socket.can_recv());
|
||||
debug!("can send: {}", socket.can_send());
|
||||
debug!("may recv: {}", socket.may_recv());
|
||||
debug!("may send: {}", socket.may_send());
|
||||
}
|
||||
|
||||
state = match state {
|
||||
State::Connecting if !socket.is_active() => {
|
||||
if listen { // Listen to a local port
|
||||
if !socket.is_open() {
|
||||
if verbose {
|
||||
debug!("Listening to {}", port);
|
||||
}
|
||||
socket.listen(port).unwrap();
|
||||
}
|
||||
} else { // Connect to a remote port
|
||||
let local_port = 49152 + random::get_u16() % 16384;
|
||||
if verbose {
|
||||
debug!("Connecting to {}:{}", address, port);
|
||||
}
|
||||
if socket.connect(cx, (address, port), local_port).is_err() {
|
||||
error!("Could not connect to {}:{}", address, port);
|
||||
return usr::shell::ExitCode::CommandError;
|
||||
}
|
||||
}
|
||||
State::Receiving
|
||||
}
|
||||
State::Sending if socket.can_recv() => {
|
||||
if verbose {
|
||||
debug!("Sending -> Receiving");
|
||||
}
|
||||
State::Receiving
|
||||
}
|
||||
State::Sending if socket.can_send() && socket.may_recv() => {
|
||||
if !read_only {
|
||||
if verbose {
|
||||
debug!("Sending ...");
|
||||
}
|
||||
if prompt {
|
||||
// Print prompt
|
||||
print!("{}>{} ", Style::color("Cyan"), Style::reset());
|
||||
}
|
||||
let line = io::stdin().read_line();
|
||||
if line.is_empty() {
|
||||
socket.close();
|
||||
} else {
|
||||
let line = line.replace("\n", "\r\n");
|
||||
socket.send_slice(line.as_ref()).expect("cannot send");
|
||||
}
|
||||
}
|
||||
State::Receiving
|
||||
}
|
||||
State::Receiving if socket.can_recv() => {
|
||||
if verbose {
|
||||
debug!("Receiving ...");
|
||||
}
|
||||
socket.recv(|data| {
|
||||
let contents = String::from_utf8_lossy(data);
|
||||
print!("{}", contents.replace("\r\n", "\n"));
|
||||
(data.len(), ())
|
||||
}).unwrap();
|
||||
State::Receiving
|
||||
}
|
||||
_ if socket.state() == TcpState::SynSent || socket.state() == TcpState::SynReceived => {
|
||||
state
|
||||
}
|
||||
State::Receiving if !socket.may_recv() && !listen => {
|
||||
if verbose {
|
||||
debug!("Break from response");
|
||||
}
|
||||
break;
|
||||
}
|
||||
State::Receiving if socket.can_send() => {
|
||||
if verbose {
|
||||
debug!("Receiving -> Sending");
|
||||
}
|
||||
State::Sending
|
||||
}
|
||||
_ if !socket.is_active() && !listen => {
|
||||
if verbose {
|
||||
debug!("Break from inactive");
|
||||
}
|
||||
break;
|
||||
}
|
||||
_ => state
|
||||
};
|
||||
|
||||
if interval > 0.0 {
|
||||
syscall::sleep(interval);
|
||||
}
|
||||
if let Some(wait_duration) = iface.poll_delay(timestamp) {
|
||||
syscall::sleep((wait_duration.total_micros() as f64) / 1000000.0);
|
||||
}
|
||||
}
|
||||
iface.remove_socket(tcp_handle);
|
||||
usr::shell::ExitCode::CommandSuccessful
|
||||
} else {
|
||||
usr::shell::ExitCode::CommandError
|
||||
}
|
||||
}
|
||||
|
||||
fn help() -> usr::shell::ExitCode {
|
||||
let csi_option = Style::color("LightCyan");
|
||||
let csi_title = Style::color("Yellow");
|
||||
let csi_reset = Style::reset();
|
||||
println!("{}Usage:{} socket {}[<host>] <port>{1}", csi_title, csi_reset, csi_option);
|
||||
println!();
|
||||
println!("{}Options:{}", csi_title, csi_reset);
|
||||
println!(" {0}-l{1}, {0}--listen{1} Listen to a local port", csi_option, csi_reset);
|
||||
println!(" {0}-v{1}, {0}--verbose{1} Increase verbosity", csi_option, csi_reset);
|
||||
println!(" {0}-p{1}, {0}--prompt{1} Display prompt", csi_option, csi_reset);
|
||||
println!(" {0}-r{1}, {0}--read-only{1} Read only connexion", csi_option, csi_reset);
|
||||
println!(" {0}-i{1}, {0}--interval <time>{1} Wait <time> between packets", csi_option, csi_reset);
|
||||
usr::shell::ExitCode::CommandSuccessful
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
use crate::{sys, usr};
|
||||
use crate::{sys, usr, debug};
|
||||
use crate::api::console::Style;
|
||||
use crate::api::syscall;
|
||||
use crate::api::random;
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::string::String;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use core::str::{self, FromStr};
|
||||
|
@ -12,15 +11,26 @@ use smoltcp::time::Instant;
|
|||
use smoltcp::wire::IpAddress;
|
||||
|
||||
pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
||||
let mut args: Vec<String> = args.iter().map(ToOwned::to_owned).map(ToOwned::to_owned).collect();
|
||||
let mut verbose = false;
|
||||
let mut args: Vec<&str> = args.iter().filter_map(|arg| {
|
||||
match *arg {
|
||||
"-v" | "--verbose" => {
|
||||
verbose = true;
|
||||
None
|
||||
}
|
||||
_ => {
|
||||
Some(*arg)
|
||||
}
|
||||
}
|
||||
}).collect();
|
||||
|
||||
// Split <host> and <port>
|
||||
if args.len() == 2 {
|
||||
if let Some(i) = args[1].find(':') {
|
||||
let arg = args[1].clone();
|
||||
let (host, path) = arg.split_at(i);
|
||||
args[1] = host.to_string();
|
||||
args.push(path[1..].to_string());
|
||||
args[1] = host;
|
||||
args.push(&path[1..]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +75,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
|||
iface.remove_socket(tcp_handle);
|
||||
return usr::shell::ExitCode::CommandError;
|
||||
}
|
||||
if sys::console::end_of_text() {
|
||||
if sys::console::end_of_text() || sys::console::end_of_transmission() {
|
||||
eprintln!();
|
||||
iface.remove_socket(tcp_handle);
|
||||
return usr::shell::ExitCode::CommandError;
|
||||
|
@ -80,7 +90,9 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
|||
state = match state {
|
||||
State::Connect if !socket.is_active() => {
|
||||
let local_port = 49152 + random::get_u16() % 16384;
|
||||
println!("Connecting to {}:{}", address, port);
|
||||
if verbose {
|
||||
debug!("Connecting to {}:{}", address, port);
|
||||
}
|
||||
if socket.connect(cx, (address, port), local_port).is_err() {
|
||||
error!("Could not connect to {}:{}", address, port);
|
||||
return usr::shell::ExitCode::CommandError;
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>MOROS: Obscure Rust Operating System</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>MOROS</h1>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue