Add network syscalls (#512)

* Add connect syscall

* Add tcp socket device

* Add socket.connect to lisp

* Rewrite tcp command to use the syscalls

* Rewrite http command to use the syscalls

* Use socket#recv_slice instead of socket#recv

* Move connect to net module

* Refactor connect

* Refactor code

* Add listen syscall

* Add accept

* Add close to FileIO

* Fix write loop

* Add poll syscall

* Rewrite socket command to use poll

* Update console polling

* Rename file handle to handle

* Remove prompt option from socket

* Make poll blocking with timeout and cancel

* Add one byte status read

* Remove poll blocking

* Fix error when connect is followed directly by close

* Change tcp and socket command arguments

* Add sleep while polling without events

* Speed up connection failure detection

* Add back some verbose output

* Fix socket listen

* Split net files

* Add UDP sockets

* Refactor host command code

* Move shared socket code to module

* Use recommended buffer size in socket file size

* Remove debug output

* Fix fallback socket size

* Update html doc

* Update network screenshot

* Update lisp manual
This commit is contained in:
Vincent Ollivier 2023-07-29 16:24:45 +02:00 committed by GitHub
parent eab33dc5c3
commit b606c064c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1287 additions and 589 deletions

View File

@ -65,6 +65,7 @@ MOROS Lisp is a Lisp-1 dialect inspired by Scheme, Clojure, and Ruby!
- `let`
- `string.join` (aliased to `str.join`), `lines`, `words`, `chars`
- `regex.match?`
- `socket.connect`, `socket.listen`, `socket.accept`
### File Library
- `read`, `write`, `append`
@ -199,3 +200,4 @@ language and reading from the filesystem.
### Unreleased
- Add file, number, string, and regex namespaces
- Add socket functions

View File

@ -61,14 +61,14 @@ The `dhcp` command configures the network automatically:
The `host` command performs DNS lookups:
> host example.com
example.com has address 93.184.216.34
93.184.216.34
## TCP
The `tcp` command connects to TCP sockets:
> tcp time.nist.gov 13 --verbose
> 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) *
@ -101,18 +101,16 @@ 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
> 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
> socket moros.cc:80
GET /test.html HTTP/1.0
Host: moros.cc
> 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
@ -136,27 +134,24 @@ And the request made with `http` is equivalent to that:
Here's a connexion to a SMTP server to send a mail:
> socket 10.0.2.2 2500 --prompt
MOROS Socket v0.1.0
> socket 10.0.2.2:2500
220 EventMachine SMTP Server
> EHLO moros.cc
HELO moros.cc
250-Ok EventMachine SMTP Server
250-NO-SOLICITING
250 SIZE 20000000
> MAIL FROM:<vinc@moros.cc>
> RCPT TO:<alice@example.com>
MAIL FROM:<vinc@moros.cc>
250 Ok
RCPT TO:<alice@example.com>
250 Ok
> DATA
DATA
354 Send it
> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nec
> diam vitae ex blandit malesuada nec a turpis.
> .
> QUIT
Subject: Test
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nec
diam vitae ex blandit malesuada nec a turpis.
.
250 Message accepted
QUIT
221 Ok
Sending a file to a server:
> socket 10.0.2.2 1234 <= /tmp/alice.txt
> socket 10.0.2.2:1234 <= /tmp/alice.txt

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -100,15 +100,15 @@ file while the standard error is kept:
> time read foo.txt => /dev/null
The standard output is implied as the source of a redirection, but it is
possible to explicitly redirect a file handle to another (TODO):
possible to explicitly redirect a handle to another (TODO):
> time read foo.txt [1]=>[3]
Or to redirect a file handle to a file:
Or to redirect a handle to a file:
> time read foo.txt [1]=> bar.txt
Or to pipe a file handle to another command:
Or to pipe a handle to another command:
> time read foo.txt [1]-> write bar.txt
@ -125,7 +125,7 @@ Redirections should be declared before piping (TODO):
> write <= req.txt => /net/http/moros.cc -> find --line href -> sort
NOTE: The following file handles are available when a process is created:
NOTE: The following handles are available when a process is created:
- `stdin(0)`
- `stdout(1)`

View File

@ -69,3 +69,21 @@ The system will reboot with `0xcafe` and halt with `0xdead`.
```rust
pub fn sleep(seconds: f64)
```
## CONNECT (0xC)
```rust
pub fn connect(handle, usize, addr: &str, port: u16) -> isize
```
## LISTEN (0xD)
```rust
pub fn listen(handle, usize, port: u16) -> isize
```
## ACCEPT (0xE)
```rust
pub fn accept(handle, usize, addr: &str) -> isize
```

View File

@ -9,9 +9,14 @@ use alloc::vec;
pub use crate::sys::fs::{FileInfo, DeviceType};
#[derive(Clone, Copy)]
pub enum IO { Read, Write }
pub trait FileIO {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, ()>;
fn write(&mut self, buf: &[u8]) -> Result<usize, ()>;
fn close(&mut self);
fn poll(&mut self, event: IO) -> bool;
}
pub fn dirname(pathname: &str) -> &str {

View File

@ -1,8 +1,12 @@
use crate::api::fs::IO;
use crate::api::process::ExitCode;
use crate::syscall;
use crate::sys::syscall::number::*;
use crate::sys::fs::FileInfo;
use smoltcp::wire::IpAddress;
use smoltcp::wire::Ipv4Address;
pub fn exit(code: ExitCode) {
unsafe { syscall!(EXIT, code as usize) };
}
@ -106,6 +110,51 @@ pub fn halt() {
stop(0xdead);
}
pub fn poll(list: &[(usize, IO)]) -> Option<(usize, IO)> {
let ptr = list.as_ptr() as usize;
let len = list.len();
let idx = unsafe { syscall!(POLL, ptr, len) } as isize;
if 0 <= idx && idx < len as isize {
Some(list[idx as usize])
} else {
None
}
}
pub fn connect(handle: usize, addr: IpAddress, port: u16) -> Result<(), ()> {
let buf = addr.as_bytes();
let ptr = buf.as_ptr() as usize;
let len = buf.len();
let res = unsafe { syscall!(CONNECT, handle, ptr, len, port) } as isize;
if res >= 0 {
Ok(())
} else {
Err(())
}
}
pub fn listen(handle: usize, port: u16) -> Result<(), ()> {
let res = unsafe { syscall!(LISTEN, handle, port) } as isize;
if res >= 0 {
Ok(())
} else {
Err(())
}
}
pub fn accept(handle: usize) -> Result<IpAddress, ()> {
let addr = IpAddress::v4(0, 0, 0, 0);
let buf = addr.as_bytes();
let ptr = buf.as_ptr() as usize;
let len = buf.len();
let res = unsafe { syscall!(ACCEPT, handle, ptr, len) } as isize;
if res >= 0 {
Ok(IpAddress::from(Ipv4Address::from_bytes(buf)))
} else {
Err(())
}
}
#[test_case]
fn test_file() {
use crate::sys::fs::{mount_mem, format_mem, dismount, OpenFlag};

View File

@ -1,7 +1,7 @@
use crate::api::clock::DATE_TIME_ZONE;
use crate::api::fs::{FileIO, IO};
use crate::sys;
use crate::sys::cmos::CMOS;
use crate::sys::fs::FileIO;
use time::{OffsetDateTime, Duration};
@ -31,9 +31,20 @@ impl FileIO for Uptime {
Err(())
}
}
fn write(&mut self, _buf: &[u8]) -> Result<usize, ()> {
unimplemented!();
}
fn close(&mut self) {
}
fn poll(&mut self, event: IO) -> bool {
match event {
IO::Read => true,
IO::Write => false,
}
}
}
// NOTE: This clock is monotonic
@ -65,9 +76,20 @@ impl FileIO for Realtime {
Err(())
}
}
fn write(&mut self, _buf: &[u8]) -> Result<usize, ()> {
unimplemented!();
}
fn close(&mut self) {
}
fn poll(&mut self, event: IO) -> bool {
match event {
IO::Read => true,
IO::Write => false,
}
}
}
// NOTE: This clock is not monotonic

View File

@ -1,5 +1,5 @@
use crate::api::clock::{DATE_TIME, DATE_TIME_LEN};
use crate::sys::fs::FileIO;
use crate::api::fs::{FileIO, IO};
use alloc::string::String;
use bit_field::BitField;
@ -83,6 +83,16 @@ impl FileIO for RTC {
CMOS::new().update_rtc(self);
Ok(buf.len())
}
fn close(&mut self) {
}
fn poll(&mut self, event: IO) -> bool {
match event {
IO::Read => true,
IO::Write => true,
}
}
}
pub struct CMOS {

View File

@ -1,5 +1,5 @@
use crate::api::fs::{FileIO, IO};
use crate::sys;
use crate::sys::fs::FileIO;
use alloc::string::String;
use alloc::string::ToString;
use core::fmt;
@ -43,6 +43,16 @@ impl FileIO for Console {
print_fmt(format_args!("{}", s));
Ok(n)
}
fn close(&mut self) {
}
fn poll(&mut self, event: IO) -> bool {
match event {
IO::Read => STDIN.lock().contains('\n'),
IO::Write => true,
}
}
}
pub fn cols() -> usize {

View File

@ -1,4 +1,4 @@
use super::{dirname, filename, realpath, FileIO};
use super::{dirname, filename, realpath, FileIO, IO};
use super::dir::Dir;
use super::file::File;
use super::block::LinkedBlock;
@ -7,6 +7,8 @@ use crate::sys::cmos::RTC;
use crate::sys::console::Console;
use crate::sys::random::Random;
use crate::sys::clock::{Uptime, Realtime};
use crate::sys::net::socket::tcp::TcpSocket;
use crate::sys::net::socket::udp::UdpSocket;
use alloc::vec;
use alloc::vec::Vec;
@ -14,24 +16,28 @@ use alloc::vec::Vec;
#[derive(PartialEq, Eq, Clone, Copy)]
#[repr(u8)]
pub enum DeviceType {
Null = 0,
File = 1,
Console = 2,
Random = 3,
Uptime = 4,
Realtime = 5,
RTC = 6,
Null = 0,
File = 1,
Console = 2,
Random = 3,
Uptime = 4,
Realtime = 5,
RTC = 6,
TcpSocket = 7,
UdpSocket = 8,
}
// Used when creating a device
impl DeviceType {
pub fn buf(self) -> Vec<u8> {
let len = match self {
DeviceType::RTC => RTC::size(),
DeviceType::Uptime => Uptime::size(),
DeviceType::Realtime => Realtime::size(),
DeviceType::Console => Console::size(),
_ => 1,
DeviceType::RTC => RTC::size(),
DeviceType::Uptime => Uptime::size(),
DeviceType::Realtime => Realtime::size(),
DeviceType::Console => Console::size(),
DeviceType::TcpSocket => TcpSocket::size(),
DeviceType::UdpSocket => UdpSocket::size(),
_ => 1,
};
let mut res = vec![0; len];
res[0] = self as u8;
@ -48,6 +54,8 @@ pub enum Device {
Uptime(Uptime),
Realtime(Realtime),
RTC(RTC),
TcpSocket(TcpSocket),
UdpSocket(UdpSocket),
}
impl From<u8> for Device {
@ -60,6 +68,8 @@ impl From<u8> for Device {
i if i == DeviceType::Uptime as u8 => Device::Uptime(Uptime::new()),
i if i == DeviceType::Realtime as u8 => Device::Realtime(Realtime::new()),
i if i == DeviceType::RTC as u8 => Device::RTC(RTC::new()),
i if i == DeviceType::TcpSocket as u8 => Device::TcpSocket(TcpSocket::new()),
i if i == DeviceType::UdpSocket as u8 => Device::UdpSocket(UdpSocket::new()),
_ => unimplemented!(),
}
}
@ -100,25 +110,57 @@ impl Device {
impl FileIO for Device {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, ()> {
match self {
Device::Null => Err(()),
Device::File(io) => io.read(buf),
Device::Console(io) => io.read(buf),
Device::Random(io) => io.read(buf),
Device::Uptime(io) => io.read(buf),
Device::Realtime(io) => io.read(buf),
Device::RTC(io) => io.read(buf),
Device::Null => Err(()),
Device::File(io) => io.read(buf),
Device::Console(io) => io.read(buf),
Device::Random(io) => io.read(buf),
Device::Uptime(io) => io.read(buf),
Device::Realtime(io) => io.read(buf),
Device::RTC(io) => io.read(buf),
Device::TcpSocket(io) => io.read(buf),
Device::UdpSocket(io) => io.read(buf),
}
}
fn write(&mut self, buf: &[u8]) -> Result<usize, ()> {
match self {
Device::Null => Ok(0),
Device::File(io) => io.write(buf),
Device::Console(io) => io.write(buf),
Device::Random(io) => io.write(buf),
Device::Uptime(io) => io.write(buf),
Device::Realtime(io) => io.write(buf),
Device::RTC(io) => io.write(buf),
Device::Null => Ok(0),
Device::File(io) => io.write(buf),
Device::Console(io) => io.write(buf),
Device::Random(io) => io.write(buf),
Device::Uptime(io) => io.write(buf),
Device::Realtime(io) => io.write(buf),
Device::RTC(io) => io.write(buf),
Device::TcpSocket(io) => io.write(buf),
Device::UdpSocket(io) => io.write(buf),
}
}
fn close(&mut self) {
match self {
Device::Null => {},
Device::File(io) => io.close(),
Device::Console(io) => io.close(),
Device::Random(io) => io.close(),
Device::Uptime(io) => io.close(),
Device::Realtime(io) => io.close(),
Device::RTC(io) => io.close(),
Device::TcpSocket(io) => io.close(),
Device::UdpSocket(io) => io.close(),
}
}
fn poll(&mut self, event: IO) -> bool {
match self {
Device::Null => false,
Device::File(io) => io.poll(event),
Device::Console(io) => io.poll(event),
Device::Random(io) => io.poll(event),
Device::Uptime(io) => io.poll(event),
Device::Realtime(io) => io.poll(event),
Device::RTC(io) => io.poll(event),
Device::TcpSocket(io) => io.poll(event),
Device::UdpSocket(io) => io.poll(event),
}
}
}

View File

@ -1,4 +1,4 @@
use super::{dirname, filename, realpath, FileIO};
use super::{dirname, filename, realpath, FileIO, IO};
use super::super_block::SuperBlock;
use super::dir_entry::DirEntry;
use super::read_dir::ReadDir;
@ -239,9 +239,20 @@ impl FileIO for Dir {
}
Ok(i)
}
fn write(&mut self, _buf: &[u8]) -> Result<usize, ()> {
Err(())
}
fn close(&mut self) {
}
fn poll(&mut self, event: IO) -> bool {
match event {
IO::Read => self.entry_index < self.entries().count() as u32,
IO::Write => true,
}
}
}
// Truncate to the given number of bytes at most while respecting char boundaries

View File

@ -1,4 +1,4 @@
use super::{dirname, filename, realpath, FileIO};
use super::{dirname, filename, realpath, FileIO, IO};
use super::dir::Dir;
use super::block::LinkedBlock;
use super::dir_entry::DirEntry;
@ -199,6 +199,16 @@ impl FileIO for File {
}
Ok(bytes)
}
fn close(&mut self) {
}
fn poll(&mut self, event: IO) -> bool {
match event {
IO::Read => self.offset < self.size,
IO::Write => true,
}
}
}
#[test_case]

View File

@ -16,7 +16,7 @@ pub use dir::Dir;
pub use dir_entry::FileInfo;
pub use file::{File, SeekFrom};
pub use block_device::{format_ata, format_mem, is_mounted, mount_ata, mount_mem, dismount};
pub use crate::api::fs::{dirname, filename, realpath, FileIO};
pub use crate::api::fs::{dirname, filename, realpath, FileIO, IO};
pub use crate::sys::ata::BLOCK_SIZE;
use dir_entry::DirEntry;
@ -26,6 +26,7 @@ use alloc::string::{String, ToString};
pub const VERSION: u8 = 1;
// TODO: Move that to API
#[derive(Clone, Copy)]
#[repr(u8)]
pub enum OpenFlag {
@ -76,10 +77,10 @@ pub fn open(path: &str, flags: usize) -> Option<Resource> {
pub fn delete(path: &str) -> Result<(), ()> {
if let Some(info) = info(path) {
if info.is_file() {
return File::delete(path);
} else if info.is_dir() {
if info.is_dir() {
return Dir::delete(path);
} else if info.is_file() || info.is_device() {
return File::delete(path);
}
}
Err(())
@ -122,6 +123,22 @@ impl FileIO for Resource {
Resource::Device(io) => io.write(buf),
}
}
fn close(&mut self) {
match self {
Resource::Dir(io) => io.close(),
Resource::File(io) => io.close(),
Resource::Device(io) => io.close(),
}
}
fn poll(&mut self, event: IO) -> bool {
match self {
Resource::Dir(io) => io.poll(event),
Resource::File(io) => io.poll(event),
Resource::Device(io) => io.poll(event),
}
}
}
pub fn canonicalize(path: &str) -> Result<String, ()> {

View File

@ -1,3 +1,6 @@
mod nic;
pub mod socket;
use crate::{sys, usr};
use alloc::sync::Arc;
@ -9,15 +12,27 @@ use smoltcp::time::Instant;
use smoltcp::wire::EthernetAddress;
use spin::Mutex;
mod rtl8139;
mod pcnet;
pub static NET: Mutex<Option<(Interface, EthernetDevice)>> = Mutex::new(None);
#[repr(u8)]
pub enum SocketStatus {
IsListening = 0,
IsActive = 1,
IsOpen = 2,
CanSend = 3,
MaySend = 4,
CanRecv = 5,
MayRecv = 6,
}
fn time() -> Instant {
Instant::from_micros((sys::clock::realtime() * 1000000.0) as i64)
}
#[derive(Clone)]
pub enum EthernetDevice {
RTL8139(rtl8139::Device),
PCNET(pcnet::Device),
RTL8139(nic::rtl8139::Device),
PCNET(nic::pcnet::Device),
//E2000,
//VirtIO,
}
@ -223,16 +238,15 @@ pub fn init() {
log!("NET {} MAC {}\n", name, mac);
let config = smoltcp::iface::Config::new(mac.into());
let time = Instant::from_micros((sys::clock::realtime() * 1000000.0) as i64);
let iface = Interface::new(config, &mut device, time);
let iface = Interface::new(config, &mut device, time());
*NET.lock() = Some((iface, device));
}
};
if let Some(io_base) = find_pci_io_base(0x10EC, 0x8139) {
add(EthernetDevice::RTL8139(rtl8139::Device::new(io_base)), "RTL8139");
add(EthernetDevice::RTL8139(nic::rtl8139::Device::new(io_base)), "RTL8139");
}
if let Some(io_base) = find_pci_io_base(0x1022, 0x2000) {
add(EthernetDevice::PCNET(pcnet::Device::new(io_base)), "PCNET");
add(EthernetDevice::PCNET(nic::pcnet::Device::new(io_base)), "PCNET");
}
}

2
src/sys/net/nic/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod rtl8139;
pub mod pcnet;

22
src/sys/net/socket/mod.rs Normal file
View File

@ -0,0 +1,22 @@
use crate::sys;
pub mod tcp;
pub mod udp;
use alloc::vec;
use lazy_static::lazy_static;
use smoltcp::iface::SocketSet;
use smoltcp::time::Duration;
use spin::Mutex;
lazy_static! {
pub static ref SOCKETS: Mutex<SocketSet<'static>> = Mutex::new(SocketSet::new(vec![]));
}
fn random_port() -> u16 {
49152 + sys::random::get_u16() % 16384
}
fn wait(duration: Duration) {
sys::time::sleep((duration.total_micros() as f64) / 1000000.0);
}

254
src/sys/net/socket/tcp.rs Normal file
View File

@ -0,0 +1,254 @@
use crate::sys;
use crate::api::fs::{FileIO, IO};
use crate::sys::net::SocketStatus;
use super::SOCKETS;
use super::{random_port, wait};
use alloc::vec;
use bit_field::BitField;
use smoltcp::iface::SocketHandle;
use smoltcp::phy::Device;
use smoltcp::socket::tcp;
use smoltcp::wire::IpAddress;
fn tcp_socket_status(socket: &tcp::Socket) -> u8 {
let mut status = 0;
status.set_bit(SocketStatus::IsListening as usize, socket.is_listening());
status.set_bit(SocketStatus::IsActive as usize, socket.is_active());
status.set_bit(SocketStatus::IsOpen as usize, socket.is_open());
status.set_bit(SocketStatus::MaySend as usize, socket.may_send());
status.set_bit(SocketStatus::CanSend as usize, socket.can_send());
status.set_bit(SocketStatus::MayRecv as usize, socket.may_recv());
status.set_bit(SocketStatus::CanRecv as usize, socket.can_recv());
status
}
#[derive(Debug, Clone)]
pub struct TcpSocket {
pub handle: SocketHandle,
}
impl TcpSocket {
pub fn size() -> usize {
if let Some((_, ref mut device)) = *sys::net::NET.lock() {
let mtu = device.capabilities().max_transmission_unit;
let eth_header = 14;
let ip_header = 20;
let tcp_header = 20;
mtu - eth_header - ip_header - tcp_header
} else {
1
}
}
pub fn new() -> Self {
let mut sockets = SOCKETS.lock();
let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer);
let handle = sockets.add(tcp_socket);
Self { handle }
}
pub fn connect(&mut self, addr: IpAddress, port: u16) -> Result<(), ()> {
let mut connecting = false;
let timeout = 5.0;
let started = sys::clock::realtime();
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
loop {
if sys::clock::realtime() - started > timeout {
return Err(());
}
let mut sockets = SOCKETS.lock();
iface.poll(sys::net::time(), device, &mut sockets);
let socket = sockets.get_mut::<tcp::Socket>(self.handle);
match socket.state() {
tcp::State::Closed => {
if connecting {
return Err(());
}
let cx = iface.context();
let dest = (addr, port);
if socket.connect(cx, dest, random_port()).is_err() {
return Err(());
}
connecting = true;
}
tcp::State::SynSent => {
}
tcp::State::Established => {
break;
}
_ => {
// Did something get sent before the connection closed?
return if socket.can_recv() {
Ok(())
} else {
Err(())
};
}
}
if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) {
wait(duration);
}
sys::time::halt();
}
}
Ok(())
}
pub fn listen(&mut self, port: u16) -> Result<(), ()> {
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SOCKETS.lock();
iface.poll(sys::net::time(), device, &mut sockets);
let socket = sockets.get_mut::<tcp::Socket>(self.handle);
if socket.listen(port).is_err() {
return Err(());
}
if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) {
wait(duration);
}
sys::time::halt();
Ok(())
} else {
Err(())
}
}
pub fn accept(&mut self) -> Result<IpAddress, ()> {
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
loop {
let mut sockets = SOCKETS.lock();
iface.poll(sys::net::time(), device, &mut sockets);
let socket = sockets.get_mut::<tcp::Socket>(self.handle);
if let Some(endpoint) = socket.remote_endpoint() {
return Ok(endpoint.addr);
}
if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) {
wait(duration);
}
sys::time::halt();
}
} else {
Err(())
}
}
}
impl FileIO for TcpSocket {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, ()> {
let timeout = 5.0;
let started = sys::clock::realtime();
let mut bytes = 0;
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SOCKETS.lock();
loop {
if sys::clock::realtime() - started > timeout {
return Err(());
}
iface.poll(sys::net::time(), device, &mut sockets);
let socket = sockets.get_mut::<tcp::Socket>(self.handle);
if buf.len() == 1 { // 1 byte status read
buf[0] = tcp_socket_status(socket);
return Ok(1);
}
if socket.can_recv() {
bytes = socket.recv_slice(buf).map_err(|_| ())?;
break;
}
if !socket.may_recv() {
break;
}
if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) {
wait(duration);
}
sys::time::halt();
}
Ok(bytes)
} else {
Err(())
}
}
fn write(&mut self, buf: &[u8]) -> Result<usize, ()> {
let timeout = 5.0;
let started = sys::clock::realtime();
let mut sent = false;
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SOCKETS.lock();
loop {
if sys::clock::realtime() - started > timeout {
return Err(());
}
iface.poll(sys::net::time(), device, &mut sockets);
let socket = sockets.get_mut::<tcp::Socket>(self.handle);
if sent {
break;
}
if socket.can_send() {
if socket.send_slice(buf.as_ref()).is_err() {
return Err(());
}
sent = true; // Break after next poll
}
if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) {
wait(duration);
}
sys::time::halt();
}
Ok(buf.len())
} else {
Err(())
}
}
fn close(&mut self) {
let mut closed = false;
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SOCKETS.lock();
loop {
iface.poll(sys::net::time(), device, &mut sockets);
let socket = sockets.get_mut::<tcp::Socket>(self.handle);
if closed {
break;
}
socket.close();
closed = true;
if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) {
wait(duration);
}
sys::time::halt();
}
}
}
fn poll(&mut self, event: IO) -> bool {
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SOCKETS.lock();
iface.poll(sys::net::time(), device, &mut sockets);
let socket = sockets.get_mut::<tcp::Socket>(self.handle);
match event {
IO::Read => socket.can_recv(),
IO::Write => socket.can_send(),
}
} else {
false
}
}
}

199
src/sys/net/socket/udp.rs Normal file
View File

@ -0,0 +1,199 @@
use crate::sys;
use crate::api::fs::{FileIO, IO};
use crate::sys::net::SocketStatus;
use super::SOCKETS;
use super::{random_port, wait};
use alloc::vec;
use bit_field::BitField;
use smoltcp::iface::SocketHandle;
use smoltcp::phy::Device;
use smoltcp::socket::udp;
use smoltcp::wire::{IpAddress, IpEndpoint, IpListenEndpoint};
fn udp_socket_status(socket: &udp::Socket) -> u8 {
let mut status = 0;
status.set_bit(SocketStatus::IsOpen as usize, socket.is_open());
status.set_bit(SocketStatus::CanSend as usize, socket.can_send());
status.set_bit(SocketStatus::CanRecv as usize, socket.can_recv());
status
}
#[derive(Debug, Clone)]
pub struct UdpSocket {
pub handle: SocketHandle,
pub remote_endpoint: Option<IpEndpoint>,
}
impl UdpSocket {
pub fn size() -> usize {
if let Some((_, ref mut device)) = *sys::net::NET.lock() {
let mtu = device.capabilities().max_transmission_unit;
let eth_header = 14;
let ip_header = 20;
let udp_header = 8;
mtu - eth_header - ip_header - udp_header
} else {
1
}
}
pub fn new() -> Self {
let mut sockets = SOCKETS.lock();
let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024]);
let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024]);
let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
let handle = sockets.add(udp_socket);
let remote_endpoint = None;
Self { handle, remote_endpoint }
}
pub fn connect(&mut self, addr: IpAddress, port: u16) -> Result<(), ()> {
let timeout = 5.0;
let started = sys::clock::realtime();
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
loop {
if sys::clock::realtime() - started > timeout {
return Err(());
}
let mut sockets = SOCKETS.lock();
iface.poll(sys::net::time(), device, &mut sockets);
let socket = sockets.get_mut::<udp::Socket>(self.handle);
if !socket.is_open() {
let local_endpoint = IpListenEndpoint::from(random_port());
socket.bind(local_endpoint).unwrap();
break;
}
if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) {
wait(duration);
}
sys::time::halt();
}
}
self.remote_endpoint = Some(IpEndpoint::new(addr, port));
Ok(())
}
pub fn listen(&mut self, _port: u16) -> Result<(), ()> {
todo!()
}
pub fn accept(&mut self) -> Result<IpAddress, ()> {
todo!()
}
}
impl FileIO for UdpSocket {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, ()> {
let timeout = 5.0;
let started = sys::clock::realtime();
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let bytes;
let mut sockets = SOCKETS.lock();
loop {
if sys::clock::realtime() - started > timeout {
return Err(());
}
iface.poll(sys::net::time(), device, &mut sockets);
let socket = sockets.get_mut::<udp::Socket>(self.handle);
if buf.len() == 1 { // 1 byte status read
buf[0] = udp_socket_status(socket);
return Ok(1);
}
if socket.can_recv() {
(bytes, _) = socket.recv_slice(buf).map_err(|_| ())?;
break;
}
if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) {
wait(duration);
}
sys::time::halt();
}
Ok(bytes)
} else {
Err(())
}
}
fn write(&mut self, buf: &[u8]) -> Result<usize, ()> {
let timeout = 5.0;
let started = sys::clock::realtime();
let mut sent = false;
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SOCKETS.lock();
loop {
if sys::clock::realtime() - started > timeout {
return Err(());
}
iface.poll(sys::net::time(), device, &mut sockets);
let socket = sockets.get_mut::<udp::Socket>(self.handle);
if sent {
break;
}
if socket.can_send() {
if let Some(remote_endpoint) = self.remote_endpoint {
if socket.send_slice(buf.as_ref(), remote_endpoint).is_err() {
return Err(());
}
} else {
return Err(());
}
sent = true; // Break after next poll
}
if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) {
wait(duration);
}
sys::time::halt();
}
Ok(buf.len())
} else {
Err(())
}
}
fn close(&mut self) {
let mut closed = false;
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SOCKETS.lock();
loop {
iface.poll(sys::net::time(), device, &mut sockets);
let socket = sockets.get_mut::<udp::Socket>(self.handle);
if closed {
break;
}
socket.close();
closed = true;
if let Some(duration) = iface.poll_delay(sys::net::time(), &sockets) {
wait(duration);
}
sys::time::halt();
}
}
}
fn poll(&mut self, event: IO) -> bool {
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SOCKETS.lock();
iface.poll(sys::net::time(), device, &mut sockets);
let socket = sockets.get_mut::<udp::Socket>(self.handle);
match event {
IO::Read => socket.can_recv(),
IO::Write => socket.can_send(),
}
} else {
false
}
}
}

View File

@ -13,7 +13,7 @@ use object::{Object, ObjectSegment};
use spin::RwLock;
use x86_64::structures::idt::InterruptStackFrameValue;
const MAX_FILE_HANDLES: usize = 64;
const MAX_HANDLES: usize = 64;
const MAX_PROCS: usize = 2; // TODO: Update this when more than one process can run at once
const MAX_PROC_SIZE: usize = 10 << 20; // 10 MB
@ -29,7 +29,7 @@ pub struct ProcessData {
env: BTreeMap<String, String>,
dir: String,
user: Option<String>,
file_handles: [Option<Box<Resource>>; MAX_FILE_HANDLES],
handles: [Option<Box<Resource>>; MAX_HANDLES],
}
impl ProcessData {
@ -37,12 +37,12 @@ impl ProcessData {
let env = BTreeMap::new();
let dir = dir.to_string();
let user = user.map(String::from);
let mut file_handles = [(); MAX_FILE_HANDLES].map(|_| None);
file_handles[0] = Some(Box::new(Resource::Device(Device::Console(Console::new())))); // stdin
file_handles[1] = Some(Box::new(Resource::Device(Device::Console(Console::new())))); // stdout
file_handles[2] = Some(Box::new(Resource::Device(Device::Console(Console::new())))); // stderr
file_handles[3] = Some(Box::new(Resource::Device(Device::Null))); // stdnull
Self { env, dir, user, file_handles }
let mut handles = [(); MAX_HANDLES].map(|_| None);
handles[0] = Some(Box::new(Resource::Device(Device::Console(Console::new())))); // stdin
handles[1] = Some(Box::new(Resource::Device(Device::Console(Console::new())))); // stdout
handles[2] = Some(Box::new(Resource::Device(Device::Console(Console::new())))); // stderr
handles[3] = Some(Box::new(Resource::Device(Device::Null))); // stdnull
Self { env, dir, user, handles }
}
}
@ -96,43 +96,43 @@ pub fn set_user(user: &str) {
proc.data.user = Some(user.into())
}
pub fn create_file_handle(file: Resource) -> Result<usize, ()> {
pub fn create_handle(file: Resource) -> Result<usize, ()> {
let mut table = PROCESS_TABLE.write();
let proc = &mut table[id()];
let min = 4; // The first 4 file handles are reserved
let max = MAX_FILE_HANDLES;
let min = 4; // The first 4 handles are reserved
let max = MAX_HANDLES;
for handle in min..max {
if proc.data.file_handles[handle].is_none() {
proc.data.file_handles[handle] = Some(Box::new(file));
if proc.data.handles[handle].is_none() {
proc.data.handles[handle] = Some(Box::new(file));
return Ok(handle);
}
}
debug!("Could not create file handle");
debug!("Could not create handle");
Err(())
}
pub fn update_file_handle(handle: usize, file: Resource) {
pub fn update_handle(handle: usize, file: Resource) {
let mut table = PROCESS_TABLE.write();
let proc = &mut table[id()];
proc.data.file_handles[handle] = Some(Box::new(file));
proc.data.handles[handle] = Some(Box::new(file));
}
pub fn delete_file_handle(handle: usize) {
pub fn delete_handle(handle: usize) {
let mut table = PROCESS_TABLE.write();
let proc = &mut table[id()];
proc.data.file_handles[handle] = None;
proc.data.handles[handle] = None;
}
pub fn file_handle(handle: usize) -> Option<Box<Resource>> {
pub fn handle(handle: usize) -> Option<Box<Resource>> {
let table = PROCESS_TABLE.read();
let proc = &table[id()];
proc.data.file_handles[handle].clone()
proc.data.handles[handle].clone()
}
pub fn file_handles() -> Vec<Option<Box<Resource>>> {
pub fn handles() -> Vec<Option<Box<Resource>>> {
let table = PROCESS_TABLE.read();
let proc = &table[id()];
proc.data.file_handles.to_vec()
proc.data.handles.to_vec()
}
pub fn code_addr() -> u64 {

View File

@ -1,5 +1,5 @@
use crate::sys;
use crate::sys::fs::FileIO;
use crate::api::fs::{FileIO, IO};
use rand::{RngCore, SeedableRng};
use rand_hc::Hc128Rng;
@ -22,9 +22,20 @@ impl FileIO for Random {
}
Ok(n)
}
fn write(&mut self, _buf: &[u8]) -> Result<usize, ()> {
unimplemented!();
}
fn close(&mut self) {
}
fn poll(&mut self, event: IO) -> bool {
match event {
IO::Read => true,
IO::Write => false,
}
}
}
pub fn get_u64() -> u64 {

View File

@ -3,9 +3,11 @@ pub mod service;
use crate::api::process::ExitCode;
use crate::sys;
use crate::sys::fs::FileInfo;
use crate::sys::fs::{FileInfo, IO};
use core::arch::asm;
use smoltcp::wire::IpAddress;
use smoltcp::wire::Ipv4Address;
/*
* Dispatching system calls
@ -68,14 +70,45 @@ pub fn dispatcher(n: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usize)
let path_ptr = sys::process::ptr_from_addr(arg1 as u64);
let path_len = arg2;
let path = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(path_ptr, path_len)) };
let args_ptr = arg3;
let args_len = arg4;
service::spawn(path, args_ptr, args_len) as usize
}
number::STOP => {
service::stop(arg1)
let code = arg1;
service::stop(code)
}
number::POLL => {
let ptr = sys::process::ptr_from_addr(arg1 as u64) as *const (usize, IO);
let len = arg2;
let list = unsafe { core::slice::from_raw_parts(ptr, len) };
service::poll(list) as usize
}
number::CONNECT => {
let handle = arg1;
let buf_ptr = sys::process::ptr_from_addr(arg2 as u64);
let buf_len = arg3;
let buf = unsafe { core::slice::from_raw_parts(buf_ptr, buf_len) };
let addr = IpAddress::from(Ipv4Address::from_bytes(buf));
let port = arg4 as u16;
service::connect(handle, addr, port) as usize
}
number::LISTEN => {
let handle = arg1;
let port = arg2 as u16;
service::listen(handle, port) as usize
}
number::ACCEPT => {
let handle = arg1;
let buf_ptr = sys::process::ptr_from_addr(arg2 as u64);
let buf_len = arg3;
let buf = unsafe { core::slice::from_raw_parts_mut(buf_ptr, buf_len) };
if let Ok(addr) = service::accept(handle) {
buf[0..buf_len].clone_from_slice(&addr.as_bytes());
0
} else {
-1 as isize as usize
}
}
_ => {
unimplemented!();

View File

@ -9,3 +9,7 @@ pub const DUP: usize = 0x8;
pub const DELETE: usize = 0x9;
pub const STOP: usize = 0xA;
pub const SLEEP: usize = 0xB;
pub const POLL: usize = 0xC;
pub const CONNECT: usize = 0xD;
pub const LISTEN: usize = 0xE;
pub const ACCEPT: usize = 0xF;

View File

@ -1,11 +1,14 @@
use crate::sys;
use crate::api::process::ExitCode;
use crate::api::fs::{FileIO, IO};
use crate::sys::fs::FileInfo;
use crate::sys::fs::FileIO;
use crate::sys::fs::Resource;
use crate::sys::fs::Device;
use crate::sys::process::Process;
use alloc::vec;
use core::arch::asm;
use smoltcp::wire::IpAddress;
pub fn exit(code: ExitCode) -> ExitCode {
sys::process::exit();
@ -43,7 +46,7 @@ pub fn open(path: &str, flags: usize) -> isize {
Err(_) => return -1,
};
if let Some(resource) = sys::fs::open(&path, flags) {
if let Ok(handle) = sys::process::create_file_handle(resource) {
if let Ok(handle) = sys::process::create_handle(resource) {
return handle as isize;
}
}
@ -51,17 +54,17 @@ pub fn open(path: &str, flags: usize) -> isize {
}
pub fn dup(old_handle: usize, new_handle: usize) -> isize {
if let Some(file) = sys::process::file_handle(old_handle) {
sys::process::update_file_handle(new_handle, *file);
if let Some(file) = sys::process::handle(old_handle) {
sys::process::update_handle(new_handle, *file);
return new_handle as isize;
}
-1
}
pub fn read(handle: usize, buf: &mut [u8]) -> isize {
if let Some(mut file) = sys::process::file_handle(handle) {
if let Some(mut file) = sys::process::handle(handle) {
if let Ok(bytes) = file.read(buf) {
sys::process::update_file_handle(handle, *file);
sys::process::update_handle(handle, *file);
return bytes as isize;
}
}
@ -69,9 +72,9 @@ pub fn read(handle: usize, buf: &mut [u8]) -> isize {
}
pub fn write(handle: usize, buf: &mut [u8]) -> isize {
if let Some(mut file) = sys::process::file_handle(handle) {
if let Some(mut file) = sys::process::handle(handle) {
if let Ok(bytes) = file.write(buf) {
sys::process::update_file_handle(handle, *file);
sys::process::update_handle(handle, *file);
return bytes as isize;
}
}
@ -79,7 +82,10 @@ pub fn write(handle: usize, buf: &mut [u8]) -> isize {
}
pub fn close(handle: usize) {
sys::process::delete_file_handle(handle);
if let Some(mut file) = sys::process::handle(handle) {
file.close();
sys::process::delete_handle(handle);
}
}
pub fn spawn(path: &str, args_ptr: usize, args_len: usize) -> ExitCode {
@ -124,3 +130,54 @@ pub fn stop(code: usize) -> usize {
}
0
}
pub fn poll(list: &[(usize, IO)]) -> isize {
for (i, (handle, event)) in list.iter().enumerate() {
if let Some(mut file) = sys::process::handle(*handle) {
if file.poll(*event) {
return i as isize;
}
}
}
-1
}
pub fn connect(handle: usize, addr: IpAddress, port: u16) -> isize {
if let Some(mut file) = sys::process::handle(handle) {
let res = match *file {
Resource::Device(Device::TcpSocket(ref mut dev)) => dev.connect(addr, port),
Resource::Device(Device::UdpSocket(ref mut dev)) => dev.connect(addr, port),
_ => Err(()),
};
if res.is_ok() {
sys::process::update_handle(handle, *file);
return 0;
}
}
-1
}
pub fn listen(handle: usize, port: u16) -> isize {
if let Some(file) = sys::process::handle(handle) {
let res = match *file {
Resource::Device(Device::TcpSocket(mut dev)) => dev.listen(port),
Resource::Device(Device::UdpSocket(mut dev)) => dev.listen(port),
_ => Err(()),
};
if res.is_ok() {
return 0;
}
}
-1
}
pub fn accept(handle: usize) -> Result<IpAddress, ()> {
if let Some(file) = sys::process::handle(handle) {
return match *file {
Resource::Device(Device::TcpSocket(mut dev)) => dev.accept(),
Resource::Device(Device::UdpSocket(mut dev)) => dev.accept(),
_ => Err(()),
};
}
Err(())
}

View File

@ -1,6 +1,6 @@
use crate::{sys, usr};
use crate::api::clock;
use crate::usr;
use crate::api::console::Style;
use crate::sys::fs::OpenFlag;
use crate::api::process::ExitCode;
use crate::api::random;
use crate::api::syscall;
@ -10,10 +10,7 @@ use bit_field::BitField;
use core::convert::TryInto;
use core::str;
use core::str::FromStr;
use smoltcp::iface::SocketSet;
use smoltcp::socket::udp;
use smoltcp::time::Instant;
use smoltcp::wire::{IpAddress, IpEndpoint, IpListenEndpoint, Ipv4Address};
use smoltcp::wire::{IpAddress, Ipv4Address};
// See RFC 1035 for implementation details
@ -106,13 +103,7 @@ impl Message {
self.header().get_bit(15)
}
/*
pub fn is_query(&self) -> bool {
!self.is_response()
}
*/
pub fn rcode(&self) -> ResponseCode {
pub fn code(&self) -> ResponseCode {
match self.header().get_bits(11..15) {
0 => ResponseCode::NoError,
1 => ResponseCode::FormatError,
@ -137,91 +128,58 @@ fn dns_address() -> Option<IpAddress> {
}
pub fn resolve(name: &str) -> Result<IpAddress, ResponseCode> {
let dns_port = 53;
let dns_address = dns_address().unwrap_or(IpAddress::v4(8, 8, 8, 8));
let server = IpEndpoint::new(dns_address, dns_port);
let addr = dns_address().unwrap_or(IpAddress::v4(8, 8, 8, 8));
let port = 53;
let query = Message::query(name, QueryType::A, QueryClass::IN);
let local_port = 49152 + random::get_u16() % 16384;
let client = IpListenEndpoint::from(local_port);
let socket_path = "/dev/net/udp";
let buf_len = if let Some(info) = syscall::info(socket_path) {
info.size() as usize
} else {
return Err(ResponseCode::NetworkError);
};
let qname = name;
let qtype = QueryType::A;
let qclass = QueryClass::IN;
let query = Message::query(qname, qtype, qclass);
#[derive(Debug)]
enum State { Bind, Query, Response }
let mut state = State::Bind;
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SocketSet::new(vec![]);
let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024]);
let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024]);
let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer);
let udp_handle = sockets.add(udp_socket);
match iface.ipv4_addr() {
None => {
return Err(ResponseCode::NetworkError);
}
Some(ip_addr) if ip_addr.is_unspecified() => {
return Err(ResponseCode::NetworkError);
}
_ => {}
let flags = OpenFlag::Device as usize;
if let Some(handle) = syscall::open(socket_path, flags) {
if syscall::connect(handle, addr, port).is_err() {
syscall::close(handle);
return Err(ResponseCode::NetworkError)
}
if syscall::write(handle, &query.datagram).is_none() {
syscall::close(handle);
return Err(ResponseCode::NetworkError)
}
let timeout = 5.0;
let started = clock::realtime();
loop {
if clock::realtime() - started > timeout {
return Err(ResponseCode::NetworkError);
}
let time = Instant::from_micros((clock::realtime() * 1000000.0) as i64);
iface.poll(time, device, &mut sockets);
let socket = sockets.get_mut::<udp::Socket>(udp_handle);
state = match state {
State::Bind if !socket.is_open() => {
socket.bind(client).unwrap();
State::Query
let mut data = vec![0; buf_len];
if let Some(bytes) = syscall::read(handle, &mut data) {
if bytes == 0 {
break;
}
State::Query if socket.can_send() => {
socket.send_slice(&query.datagram, server).expect("cannot send");
State::Response
}
State::Response if socket.can_recv() => {
let (data, _) = socket.recv().expect("cannot receive");
let message = Message::from(data);
if message.id() == query.id() && message.is_response() {
return match message.rcode() {
ResponseCode::NoError => {
// TODO: Parse the datagram instead of
// extracting the last 4 bytes.
//let rdata = message.answer().rdata();
let n = message.datagram.len();
let rdata = &message.datagram[(n - 4)..];
data.resize(bytes, 0);
Ok(IpAddress::from(Ipv4Address::from_bytes(rdata)))
}
rcode => {
Err(rcode)
}
let message = Message::from(&data);
if message.id() == query.id() && message.is_response() {
syscall::close(handle);
return match message.code() {
ResponseCode::NoError => {
// TODO: Parse the datagram instead of extracting
// the last 4 bytes
let n = message.datagram.len();
let data = &message.datagram[(n - 4)..];
Ok(IpAddress::from(Ipv4Address::from_bytes(data)))
}
code => {
Err(code)
}
}
state
}
_ => state
};
if let Some(wait_duration) = iface.poll_delay(time, &sockets) {
syscall::sleep((wait_duration.total_micros() as f64) / 1000000.0);
} else {
break;
}
}
} else {
Err(ResponseCode::NetworkError)
syscall::close(handle);
}
Err(ResponseCode::NetworkError)
}
pub fn main(args: &[&str]) -> Result<(), ExitCode> {
@ -233,7 +191,7 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
let domain = args[1];
match resolve(domain) {
Ok(addr) => {
println!("{} has address {}", domain, addr);
println!("{}", addr);
Ok(())
}
Err(e) => {

View File

@ -1,15 +1,13 @@
use crate::{sys, usr};
use crate::api::console::Style;
use crate::api::clock;
use crate::api::process::ExitCode;
use crate::api::random;
use crate::api::syscall;
use crate::sys::fs::OpenFlag;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec;
use core::str::{self, FromStr};
use smoltcp::iface::SocketSet;
use smoltcp::socket::tcp;
use smoltcp::time::Instant;
use smoltcp::wire::IpAddress;
#[derive(Debug)]
@ -19,7 +17,6 @@ struct URL {
pub path: String,
}
enum SessionState { Connect, Request, Response }
enum ResponseState { Headers, Body }
impl URL {
@ -103,9 +100,17 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
let url = "http://".to_string() + host + path;
let url = URL::parse(&url).expect("invalid URL format");
let address = if url.host.ends_with(char::is_numeric) {
IpAddress::from_str(&url.host).expect("invalid address format")
let port = url.port;
let addr = if url.host.ends_with(char::is_numeric) {
match IpAddress::from_str(&url.host) {
Ok(ip_addr) => {
ip_addr
}
Err(_) => {
error!("Invalid address format");
return Err(ExitCode::UsageError);
}
}
} else {
match usr::host::resolve(&url.host) {
Ok(ip_addr) => {
@ -118,117 +123,92 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
}
};
let mut session_state = SessionState::Connect;
let socket_path = "/dev/net/tcp";
let buf_len = if let Some(info) = syscall::info(socket_path) {
info.size() as usize
} else {
error!("Could not open '{}'", socket_path);
return Err(ExitCode::Failure);
};
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SocketSet::new(vec![]);
let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer);
let tcp_handle = sockets.add(tcp_socket);
let flags = OpenFlag::Device as usize;
if let Some(handle) = syscall::open(socket_path, flags) {
if syscall::connect(handle, addr, port).is_err() {
error!("Could not connect to {}:{}", addr, port);
syscall::close(handle);
return Err(ExitCode::Failure);
}
let req = vec![
format!("GET {} HTTP/1.1\r\n", url.path),
format!("Host: {}\r\n", url.host),
format!("User-Agent: MOROS/{}\r\n", env!("CARGO_PKG_VERSION")),
format!("Connection: close\r\n"),
format!("\r\n"),
];
if is_verbose {
print!("{}", csi_verbose);
for line in &req {
print!("> {}", line);
}
print!("{}", csi_reset);
}
let req = req.join("");
syscall::write(handle, &req.as_bytes());
let mut last_received_at = clock::realtime();
let mut response_state = ResponseState::Headers;
loop {
if clock::realtime() - last_received_at > timeout {
error!("Timeout reached");
return Err(ExitCode::Failure);
}
if sys::console::end_of_text() || sys::console::end_of_transmission() {
eprintln!();
syscall::close(handle);
return Err(ExitCode::Failure);
}
let time = Instant::from_micros((clock::realtime() * 1000000.0) as i64);
iface.poll(time, device, &mut sockets);
let socket = sockets.get_mut::<tcp::Socket>(tcp_handle);
let cx = iface.context();
session_state = match session_state {
SessionState::Connect if !socket.is_active() => {
let local_port = 49152 + random::get_u16() % 16384;
if is_verbose {
print!("{}", csi_verbose);
println!("* Connecting to {}:{}", address, url.port);
print!("{}", csi_reset);
}
if socket.connect(cx, (address, url.port), local_port).is_err() {
error!("Could not connect to {}:{}", address, url.port);
return Err(ExitCode::Failure);
}
SessionState::Request
}
SessionState::Request if socket.may_send() => {
let http_get = "GET ".to_string() + &url.path + " HTTP/1.1\r\n";
let http_host = "Host: ".to_string() + &url.host + "\r\n";
let http_ua = "User-Agent: MOROS/".to_string() + env!("CARGO_PKG_VERSION") + "\r\n";
let http_connection = "Connection: close\r\n".to_string();
if is_verbose {
print!("{}", csi_verbose);
print!("> {}", http_get);
print!("> {}", http_host);
print!("> {}", http_ua);
print!("> {}", http_connection);
println!(">");
print!("{}", csi_reset);
}
socket.send_slice(http_get.as_ref()).expect("cannot send");
socket.send_slice(http_host.as_ref()).expect("cannot send");
socket.send_slice(http_ua.as_ref()).expect("cannot send");
socket.send_slice(http_connection.as_ref()).expect("cannot send");
socket.send_slice(b"\r\n").expect("cannot send");
SessionState::Response
}
SessionState::Response if socket.can_recv() => {
socket.recv(|data| {
last_received_at = clock::realtime();
let n = data.len();
let mut i = 0;
while i < n {
match response_state {
ResponseState::Headers => {
let mut j = i;
while j < n {
if data[j] == b'\n' {
break;
}
j += 1;
}
let line = String::from_utf8_lossy(&data[i..j]); // TODO: check i == j
if is_verbose {
if i == 0 {
print!("{}", csi_verbose);
}
println!("< {}", line);
}
if line.trim().is_empty() {
if is_verbose {
print!("{}", csi_reset);
}
response_state = ResponseState::Body;
}
i = j + 1;
}
ResponseState::Body => {
syscall::write(1, &data[i..n]);
break;
}
}
}
(data.len(), ())
}).unwrap();
SessionState::Response
}
SessionState::Response if !socket.may_recv() => {
let mut data = vec![0; buf_len];
if let Some(n) = syscall::read(handle, &mut data) {
if n == 0 {
break;
}
_ => session_state
};
if let Some(wait_duration) = iface.poll_delay(time, &sockets) {
syscall::sleep((wait_duration.total_micros() as f64) / 1000000.0);
data.resize(n, 0);
let mut i = 0;
while i < n {
match response_state {
ResponseState::Headers => {
let mut j = i;
while j < n {
if data[j] == b'\n' {
break;
}
j += 1;
}
let line = String::from_utf8_lossy(&data[i..j]); // TODO: check i == j
if is_verbose {
if i == 0 {
print!("{}", csi_verbose);
}
println!("< {}", line);
}
if line.trim().is_empty() {
if is_verbose {
print!("{}", csi_reset);
}
response_state = ResponseState::Body;
}
i = j + 1;
}
ResponseState::Body => {
// NOTE: The buffer may not be convertible to a UTF-8 string
// so we write it to STDOUT directly instead of using print.
syscall::write(1, &data[i..n]);
break;
}
}
}
} else {
error!("Could not read from {}:{}", addr, port);
syscall::close(handle);
return Err(ExitCode::Failure);
}
}
syscall::close(handle);
Ok(())
} else {
Err(ExitCode::Failure)

View File

@ -27,13 +27,16 @@ pub fn copy_files(verbose: bool) {
copy_file("/bin/reboot", include_bytes!("../../dsk/bin/reboot"), verbose);
copy_file("/bin/sleep", include_bytes!("../../dsk/bin/sleep"), verbose);
create_dir("/dev/clk", verbose); // Clocks
create_dir("/dev/clk", verbose); // Clock
create_dev("/dev/clk/uptime", DeviceType::Uptime, verbose);
create_dev("/dev/clk/realtime", DeviceType::Realtime, verbose);
create_dev("/dev/rtc", DeviceType::RTC, verbose);
create_dev("/dev/null", DeviceType::Null, verbose);
create_dev("/dev/random", DeviceType::Random, verbose);
create_dev("/dev/console", DeviceType::Console, verbose);
create_dir("/dev/net", verbose); // Network
create_dev("/dev/net/tcp", DeviceType::TcpSocket, verbose);
create_dev("/dev/net/udp", DeviceType::UdpSocket, verbose);
copy_file("/ini/banner.txt", include_bytes!("../../dsk/ini/banner.txt"), verbose);
copy_file("/ini/boot.sh", include_bytes!("../../dsk/ini/boot.sh"), verbose);

View File

@ -74,6 +74,9 @@ pub fn default_env() -> Rc<RefCell<Env>> {
data.insert("file.read".to_string(), Exp::Primitive(primitive::lisp_file_read));
data.insert("file.write".to_string(), Exp::Primitive(primitive::lisp_file_write));
data.insert("file.close".to_string(), Exp::Primitive(primitive::lisp_file_close));
data.insert("socket.connect".to_string(), Exp::Primitive(primitive::lisp_socket_connect));
data.insert("socket.listen".to_string(), Exp::Primitive(primitive::lisp_socket_listen));
data.insert("socket.accept".to_string(), Exp::Primitive(primitive::lisp_socket_accept));
// Setup autocompletion
*FUNCTIONS.lock() = data.keys().cloned().chain(BUILT_INS.map(String::from)).collect();

View File

@ -17,6 +17,8 @@ use alloc::vec;
use core::cmp::Ordering::Equal;
use core::convert::TryFrom;
use core::convert::TryInto;
use core::str::FromStr;
use smoltcp::wire::IpAddress;
pub fn lisp_eq(args: &[Exp]) -> Result<Exp, Err> {
Ok(Exp::Bool(numbers(args)?.windows(2).all(|nums| nums[0] == nums[1])))
@ -477,3 +479,44 @@ pub fn lisp_file_write(args: &[Exp]) -> Result<Exp, Err> {
_ => expected!("second argument to be a list")
}
}
pub fn lisp_socket_connect(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 3);
let kind = string(&args[0])?;
let addr_str = string(&args[1])?;
let addr = match IpAddress::from_str(&addr_str) {
Ok(addr) => addr,
Err(()) => return expected!("valid IP address"),
};
let port: usize = number(&args[2])?.try_into()?;
let flags = OpenFlag::Device as usize;
if let Some(handle) = syscall::open(&format!("/dev/net/{}", kind), flags) {
if syscall::connect(handle, addr, port as u16).is_ok() {
return Ok(Exp::Num(Number::from(handle)));
}
}
could_not!("connect to {}:{}", addr, port)
}
pub fn lisp_socket_listen(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let kind = string(&args[0])?;
let port: usize = number(&args[1])?.try_into()?;
let flags = OpenFlag::Device as usize;
if let Some(handle) = syscall::open(&format!("/dev/net/{}", kind), flags) {
if syscall::listen(handle, port as u16).is_ok() {
return Ok(Exp::Num(Number::from(handle)));
}
}
could_not!("listen to 0.0.0.0:{}", port)
}
pub fn lisp_socket_accept(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let handle: usize = number(&args[0])?.try_into()?;
if let Ok(addr) = syscall::accept(handle) {
Ok(Exp::Str(format!("{}", addr)))
} else {
could_not!("accept connections")
}
}

View File

@ -6,6 +6,7 @@ use crate::api::syscall;
use crate::api::process::ExitCode;
use alloc::borrow::ToOwned;
use alloc::format;
use alloc::vec::Vec;
use core::convert::TryInto;
@ -28,6 +29,9 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
// TODO: Create device drivers for `/net` hardcoded commands
if path.starts_with("/net/") {
let csi_option = Style::color("LightCyan");
let csi_title = Style::color("Yellow");
let csi_reset = Style::reset();
// Examples:
// > read /net/http/example.com/articles
// > read /net/http/example.com:8080/articles/index.html
@ -35,18 +39,25 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
// > read /net/tcp/time.nist.gov:13
let parts: Vec<_> = path.split('/').collect();
if parts.len() < 4 {
eprintln!("Usage: read /net/http/<host>/<path>");
println!("{}Usage:{} read {}/net/<proto>/<host>[:<port>]/<path>{1}", csi_title, csi_reset, csi_option);
Err(ExitCode::Failure)
} else {
let host = parts[3];
match parts[2] {
"tcp" => {
let host = parts[3];
usr::tcp::main(&["tcp", host])
if host.contains(':') {
usr::tcp::main(&["tcp", host])
} else {
error!("Missing port number");
Err(ExitCode::Failure)
}
}
"daytime" => {
let host = parts[3];
let port = "13";
usr::tcp::main(&["tcp", host, port])
if host.contains(':') {
usr::tcp::main(&["tcp", host])
} else {
usr::tcp::main(&["tcp", &format!("{}:13", host)])
}
}
"http" => {
let host = parts[3];

View File

@ -252,7 +252,7 @@ fn cmd_proc(args: &[&str]) -> Result<(), ExitCode> {
Ok(())
}
"files" => {
for (i, handle) in sys::process::file_handles().iter().enumerate() {
for (i, handle) in sys::process::handles().iter().enumerate() {
if let Some(resource) = handle {
println!("{}: {:?}", i, resource);
}
@ -301,8 +301,8 @@ fn cmd_alias(args: &[&str], config: &mut Config) -> Result<(), ExitCode> {
let csi_option = Style::color("LightCyan");
let csi_title = Style::color("Yellow");
let csi_reset = Style::reset();
println!("{}Usage:{} alias {}<key> <val>{1}", csi_title, csi_reset, csi_option);
return Err(ExitCode::Failure);
eprintln!("{}Usage:{} alias {}<key> <val>{1}", csi_title, csi_reset, csi_option);
return Err(ExitCode::UsageError);
}
config.aliases.insert(args[1].to_string(), args[2].to_string());
Ok(())
@ -313,8 +313,8 @@ fn cmd_unalias(args: &[&str], config: &mut Config) -> Result<(), ExitCode> {
let csi_option = Style::color("LightCyan");
let csi_title = Style::color("Yellow");
let csi_reset = Style::reset();
println!("{}Usage:{} unalias {}<key>{1}", csi_title, csi_reset, csi_option);
return Err(ExitCode::Failure);
eprintln!("{}Usage:{} unalias {}<key>{1}", csi_title, csi_reset, csi_option);
return Err(ExitCode::UsageError);
}
if config.aliases.remove(&args[1].to_string()).is_none() {
@ -330,8 +330,8 @@ fn cmd_set(args: &[&str], config: &mut Config) -> Result<(), ExitCode> {
let csi_option = Style::color("LightCyan");
let csi_title = Style::color("Yellow");
let csi_reset = Style::reset();
println!("{}Usage:{} set {}<key> <val>{1}", csi_title, csi_reset, csi_option);
return Err(ExitCode::Failure);
eprintln!("{}Usage:{} set {}<key> <val>{1}", csi_title, csi_reset, csi_option);
return Err(ExitCode::UsageError);
}
config.env.insert(args[1].to_string(), args[2].to_string());
@ -343,8 +343,8 @@ fn cmd_unset(args: &[&str], config: &mut Config) -> Result<(), ExitCode> {
let csi_option = Style::color("LightCyan");
let csi_title = Style::color("Yellow");
let csi_reset = Style::reset();
println!("{}Usage:{} unset {}<key>{1}", csi_title, csi_reset, csi_option);
return Err(ExitCode::Failure);
eprintln!("{}Usage:{} unset {}<key>{1}", csi_title, csi_reset, csi_option);
return Err(ExitCode::UsageError);
}
if config.env.remove(&args[1].to_string()).is_none() {
@ -380,7 +380,7 @@ fn exec_with_config(cmd: &str, config: &mut Config) -> Result<(), ExitCode> {
let mut args: Vec<&str> = args.iter().map(String::as_str).collect();
// Redirections
let mut restore_file_handles = false;
let mut restore_handles = false;
let mut n = args.len();
let mut i = 0;
loop {
@ -417,7 +417,7 @@ fn exec_with_config(cmd: &str, config: &mut Config) -> Result<(), ExitCode> {
continue;
}
// Parse file handles
// Parse handles
let mut num = String::new();
for c in args[i].chars() {
match c {
@ -438,10 +438,10 @@ fn exec_with_config(cmd: &str, config: &mut Config) -> Result<(), ExitCode> {
}
if is_fat_arrow { // Redirections
restore_file_handles = true;
restore_handles = true;
if !num.is_empty() {
// if let Ok(right_handle) = num.parse() {}
println!("Redirecting to a file handle has not been implemented yet");
println!("Redirecting to a handle has not been implemented yet");
return Err(ExitCode::Failure);
} else {
if i == n - 1 {
@ -538,7 +538,7 @@ fn exec_with_config(cmd: &str, config: &mut Config) -> Result<(), ExitCode> {
// TODO: Remove this when redirections are done in spawned process
if restore_file_handles {
if restore_handles {
for i in 0..3 {
api::fs::reopen("/dev/console", i, false).ok();
}

View File

@ -1,39 +1,30 @@
use crate::{sys, usr, debug};
use crate::api::console::Style;
use crate::api::clock;
use crate::api::io;
use crate::api::fs::IO;
use crate::api::process::ExitCode;
use crate::api::random;
use crate::api::syscall;
use crate::sys::fs::OpenFlag;
use crate::sys::net::SocketStatus;
use alloc::format;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use bit_field::BitField;
use core::str::{self, FromStr};
use smoltcp::iface::SocketSet;
use smoltcp::socket::tcp;
use smoltcp::time::Instant;
use smoltcp::wire::IpAddress;
pub fn main(args: &[&str]) -> Result<(), 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| {
let args: Vec<&str> = args.iter().filter_map(|arg| {
match *arg {
"-l" | "--listen" => {
listen = true;
None
}
"-p" | "--prompt" => {
prompt = true;
None
}
"-r" | "--read-only" => {
"-r" | "--read" => {
read_only = true;
None
}
@ -41,45 +32,32 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
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");
if verbose {
println!("MOROS Socket v0.2.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 (host, path) = args[1].split_at(i);
args[1] = host;
args.push(&path[1..]);
}
}
if args.len() != required_args_count {
if args.len() != 2 {
help();
return Err(ExitCode::UsageError);
}
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) {
let (host, port) = match args[1].split_once(':') {
Some((h, p)) => (h, p),
None => ("0.0.0.0", args[1]),
};
let port: u16 = match port.parse() {
Ok(n) => n,
Err(_) => {
eprint!("Could not parse port");
return Err(ExitCode::UsageError);
}
};
let addr = if host.ends_with(char::is_numeric) {
IpAddress::from_str(host).expect("invalid address format")
} else {
match usr::host::resolve(host) {
@ -93,128 +71,82 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
}
};
#[derive(Debug)]
enum State { Connecting, Sending, Receiving }
let mut state = State::Connecting;
let socket_path = "/dev/net/tcp";
let buf_len = if let Some(info) = syscall::info(socket_path) {
info.size() as usize
} else {
error!("Could not open '{}'", socket_path);
return Err(ExitCode::Failure);
};
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SocketSet::new(vec![]);
let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer);
let tcp_handle = sockets.add(tcp_socket);
let mut connected = false;
let stdin = 0;
let stdout = 1;
let flags = OpenFlag::Device as usize;
if let Some(handle) = syscall::open(socket_path, flags) {
if listen {
if syscall::listen(handle, port).is_err() {
error!("Could not listen to {}:{}", addr, port);
syscall::close(handle);
return Err(ExitCode::Failure);
}
if verbose {
debug!("Listening to {}:{}", addr, port);
}
} else {
if syscall::connect(handle, addr, port).is_ok() {
connected = true;
} else {
error!("Could not connect to {}:{}", addr, port);
syscall::close(handle);
return Err(ExitCode::Failure);
}
if verbose {
debug!("Connected to {}:{}", addr, port);
}
}
loop {
if sys::console::end_of_text() || sys::console::end_of_transmission() {
eprintln!();
return Err(ExitCode::Failure);
println!();
break;
}
let time = Instant::from_micros((clock::realtime() * 1000000.0) as i64);
iface.poll(time, device, &mut sockets);
let socket = sockets.get_mut::<tcp::Socket>(tcp_handle);
let cx = iface.context();
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());
if listen && !connected {
if syscall::accept(handle).is_ok() {
connected = true;
} else {
syscall::sleep(0.01);
continue;
}
}
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 Err(ExitCode::Failure);
let list = vec![(stdin, IO::Read), (handle, IO::Read)];
if let Some((h, _)) = syscall::poll(&list) {
if h == stdin {
let line = io::stdin().read_line().replace("\n", "\r\n");
syscall::write(handle, &line.as_bytes());
} else {
let mut data = vec![0; buf_len];
if let Some(bytes) = syscall::read(handle, &mut data) {
data.resize(bytes, 0);
syscall::write(stdout, &data);
}
}
} else {
syscall::sleep(0.01);
if connected {
let mut data = vec![0; 1]; // 1 byte status read
match syscall::read(handle, &mut data) {
Some(1) if !data[0].get_bit(SocketStatus::MayRecv as usize) => {
break; // recv closed
}
_ => continue,
}
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() == tcp::State::SynSent || socket.state() == tcp::State::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(time, &sockets) {
syscall::sleep((wait_duration.total_micros() as f64) / 1000000.0);
}
}
syscall::close(handle);
Ok(())
} else {
Err(ExitCode::Failure)
@ -225,12 +157,10 @@ fn help() {
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!("{}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);
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}-r{1}, {0}--read{1} Read only connexion", csi_option, csi_reset);
}

View File

@ -1,22 +1,19 @@
use crate::{sys, usr, debug};
use crate::{sys, usr};
use crate::api::console::Style;
use crate::api::clock;
use crate::api::process::ExitCode;
use crate::api::random;
use crate::api::syscall;
use crate::sys::fs::OpenFlag;
use alloc::string::String;
use alloc::format;
use alloc::vec;
use alloc::vec::Vec;
use core::str::{self, FromStr};
use smoltcp::iface::SocketSet;
use smoltcp::socket::tcp;
use smoltcp::time::Instant;
use core::str;
use core::str::FromStr;
use smoltcp::wire::IpAddress;
pub fn main(args: &[&str]) -> Result<(), ExitCode> {
let mut verbose = false;
let mut args: Vec<&str> = args.iter().filter_map(|arg| {
let args: Vec<&str> = args.iter().filter_map(|arg| {
match *arg {
"-v" | "--verbose" => {
verbose = true;
@ -28,25 +25,26 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
}
}).collect();
// Split <host> and <port>
if args.len() == 2 {
if let Some(i) = args[1].find(':') {
let (host, path) = args[1].split_at(i);
args[1] = host;
args.push(&path[1..]);
}
}
if args.len() != 3 {
if args.len() != 2 {
help();
return Err(ExitCode::UsageError);
}
let host = &args[1];
let port: u16 = args[2].parse().expect("Could not parse port");
let request = "";
let address = if host.ends_with(char::is_numeric) {
let (host, port) = match args[1].split_once(':') {
Some((h, p)) => (h, p),
None => {
help();
return Err(ExitCode::UsageError);
}
};
let port: u16 = match port.parse() {
Ok(n) => n,
Err(_) => {
eprint!("Could not parse port");
return Err(ExitCode::UsageError);
}
};
let addr = if host.ends_with(char::is_numeric) {
IpAddress::from_str(host).expect("invalid address format")
} else {
match usr::host::resolve(host) {
@ -54,77 +52,50 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
ip_addr
}
Err(e) => {
error!("Could not resolve host: {:?}", e);
error!("Could not resolve host {:?}", e);
return Err(ExitCode::Failure);
}
}
};
enum State { Connect, Request, Response }
let mut state = State::Connect;
let socket_path = "/dev/net/tcp";
let buf_len = if let Some(info) = syscall::info(socket_path) {
info.size() as usize
} else {
error!("Could not open '{}'", socket_path);
return Err(ExitCode::Failure);
};
if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SocketSet::new(vec![]);
let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer);
let tcp_handle = sockets.add(tcp_socket);
let timeout = 5.0;
let started = clock::realtime();
let flags = OpenFlag::Device as usize;
if let Some(handle) = syscall::open(socket_path, flags) {
if syscall::connect(handle, addr, port).is_err() {
error!("Could not connect to {}:{}", addr, port);
syscall::close(handle);
return Err(ExitCode::Failure);
}
if verbose {
debug!("Connected to {}:{}", addr, port);
}
loop {
if clock::realtime() - started > timeout {
error!("Timeout reached");
return Err(ExitCode::Failure);
}
if sys::console::end_of_text() || sys::console::end_of_transmission() {
eprintln!();
syscall::close(handle);
return Err(ExitCode::Failure);
}
let time = Instant::from_micros((clock::realtime() * 1000000.0) as i64);
iface.poll(time, device, &mut sockets);
let socket = sockets.get_mut::<tcp::Socket>(tcp_handle);
let cx = iface.context();
state = match state {
State::Connect if !socket.is_active() => {
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 Err(ExitCode::Failure);
}
State::Request
}
State::Request if socket.may_send() => {
if !request.is_empty() {
socket.send_slice(request.as_ref()).expect("cannot send");
}
State::Response
}
State::Response if socket.can_recv() => {
socket.recv(|data| {
let contents = String::from_utf8_lossy(data);
for line in contents.lines() {
println!("{}", line);
}
(data.len(), ())
}).unwrap();
State::Response
}
State::Response if !socket.may_recv() => {
let mut data = vec![0; buf_len];
if let Some(bytes) = syscall::read(handle, &mut data) {
if bytes == 0 {
break;
}
_ => state
};
if let Some(wait_duration) = iface.poll_delay(time, &sockets) {
syscall::sleep((wait_duration.total_micros() as f64) / 1000000.0);
data.resize(bytes, 0);
syscall::write(1, &data);
} else {
error!("Could not read from {}:{}", addr, port);
syscall::close(handle);
return Err(ExitCode::Failure);
}
}
syscall::close(handle);
Ok(())
} else {
Err(ExitCode::Failure)
@ -135,5 +106,5 @@ fn help() {
let csi_option = Style::color("LightCyan");
let csi_title = Style::color("Yellow");
let csi_reset = Style::reset();
println!("{}Usage:{} tcp {}<host> <port>{1}", csi_title, csi_reset, csi_option);
println!("{}Usage:{} tcp {}<host>:<port>{1}", csi_title, csi_reset, csi_option);
}

View File

@ -84,6 +84,7 @@ of the Shell.</p>
<li><code>let</code></li>
<li><code>string.join</code> (aliased to <code>str.join</code>), <code>lines</code>, <code>words</code>, <code>chars</code></li>
<li><code>regex.match?</code></li>
<li><code>socket.connect</code>, <code>socket.listen</code>, <code>socket.accept</code></li>
</ul>
<h3>File Library</h3>
@ -239,6 +240,7 @@ language and reading from the filesystem.</p>
<ul>
<li>Add file, number, string, and regex namespaces</li>
<li>Add socket functions</li>
</ul>
</body>
</html>

View File

@ -75,14 +75,14 @@ dns: 10.0.2.3
<p>The <code>host</code> command performs DNS lookups:</p>
<pre><code>&gt; host example.com
example.com has address 93.184.216.34
93.184.216.34
</code></pre>
<h2>TCP</h2>
<p>The <code>tcp</code> command connects to TCP sockets:</p>
<pre><code>&gt; tcp time.nist.gov 13 --verbose
<pre><code>&gt; 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) *
@ -119,19 +119,17 @@ like the <code>netcat</code> command on Unix.</p>
<p>For example the request made with <code>tcp</code> above is equivalent to this:</p>
<pre><code>&gt; socket time.nist.gov 13 --read-only
<pre><code>&gt; socket time.nist.gov:13 --read-only
59710 22-05-11 21:44:52 50 0 0 359.3 UTC(NIST) *
</code></pre>
<p>And the request made with <code>http</code> is equivalent to that:</p>
<pre><code>&gt; socket moros.cc 80 --prompt
MOROS Socket v0.1.0
<pre><code>&gt; socket moros.cc:80
GET /test.html HTTP/1.0
Host: moros.cc
&gt; GET /test.html HTTP/1.0
&gt; Host: moros.cc
&gt;
HTTP/1.1 200 OK
Server: nginx
Date: Wed, 11 May 2022 21:46:34 GMT
@ -156,31 +154,28 @@ Accept-Ranges: bytes
<p>Here&#39;s a connexion to a SMTP server to send a mail:</p>
<pre><code>&gt; socket 10.0.2.2 2500 --prompt
MOROS Socket v0.1.0
<pre><code>&gt; socket 10.0.2.2:2500
220 EventMachine SMTP Server
&gt; EHLO moros.cc
HELO moros.cc
250-Ok EventMachine SMTP Server
250-NO-SOLICITING
250 SIZE 20000000
&gt; MAIL FROM:&lt;vinc@moros.cc&gt;
&gt; RCPT TO:&lt;alice@example.com&gt;
MAIL FROM:&lt;vinc@moros.cc&gt;
250 Ok
RCPT TO:&lt;alice@example.com&gt;
250 Ok
&gt; DATA
DATA
354 Send it
&gt; Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nec
&gt; diam vitae ex blandit malesuada nec a turpis.
&gt; .
&gt; QUIT
Subject: Test
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nec
diam vitae ex blandit malesuada nec a turpis.
.
250 Message accepted
QUIT
221 Ok
</code></pre>
<p>Sending a file to a server:</p>
<pre><code>&gt; socket 10.0.2.2 1234 &lt;= /tmp/alice.txt
<pre><code>&gt; socket 10.0.2.2:1234 &lt;= /tmp/alice.txt
</code></pre>
</body>
</html>

View File

@ -119,17 +119,17 @@ file while the standard error is kept:</p>
</code></pre>
<p>The standard output is implied as the source of a redirection, but it is
possible to explicitly redirect a file handle to another (TODO):</p>
possible to explicitly redirect a handle to another (TODO):</p>
<pre><code>&gt; time read foo.txt [1]=&gt;[3]
</code></pre>
<p>Or to redirect a file handle to a file:</p>
<p>Or to redirect a handle to a file:</p>
<pre><code>&gt; time read foo.txt [1]=&gt; bar.txt
</code></pre>
<p>Or to pipe a file handle to another command:</p>
<p>Or to pipe a handle to another command:</p>
<pre><code>&gt; time read foo.txt [1]-&gt; write bar.txt
</code></pre>
@ -150,7 +150,7 @@ swapped and the standard input is implied (TODO):</p>
<pre><code>&gt; write &lt;= req.txt =&gt; /net/http/moros.cc -&gt; find --line href -&gt; sort
</code></pre>
<p>NOTE: The following file handles are available when a process is created:</p>
<p>NOTE: The following handles are available when a process is created:</p>
<ul>
<li><code>stdin(0)</code></li>

View File

@ -66,5 +66,20 @@
<pre><code class="rust">pub fn sleep(seconds: f64)
</code></pre>
<h2>CONNECT (0xC)</h2>
<pre><code class="rust">pub fn connect(handle, usize, addr: &amp;str, port: u16) -&gt; isize
</code></pre>
<h2>LISTEN (0xD)</h2>
<pre><code class="rust">pub fn listen(handle, usize, port: u16) -&gt; isize
</code></pre>
<h2>ACCEPT (0xE)</h2>
<pre><code class="rust">pub fn accept(handle, usize, addr: &amp;str) -&gt; isize
</code></pre>
</body>
</html>