mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
Improve clarity of PROTOCOL.md
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
[unstable]
|
||||
build-std = ["core"]
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "no-alloc-network-test"
|
||||
version = "0.1.0"
|
||||
@@ -1,14 +1,12 @@
|
||||
[package]
|
||||
name = "no-alloc-network-test"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
include.workspace = true
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
authors = ["ASTATIN3"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/Astatin3/unshell"
|
||||
include = ["LICENSE", "**/*.rs", "Cargo.toml"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
[workspace]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
[dependencies]
|
||||
+344
-146
@@ -1,6 +1,6 @@
|
||||
//! # TCP Network Stack using Raw Syscalls
|
||||
//!
|
||||
//! A TCP server using raw syscalls via libc - no std::net, no smoltcp.
|
||||
//! A TCP server using raw x86/64 Linux syscalls via inline assembly - no libc, no std.
|
||||
//!
|
||||
//! ## Usage
|
||||
//! ```bash
|
||||
@@ -9,195 +9,393 @@
|
||||
//! ```
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::mem::zeroed;
|
||||
use libc::{
|
||||
AF_INET, SOCK_STREAM, accept, bind, close, listen, read, sockaddr_in, socket, socklen_t, write,
|
||||
};
|
||||
use core::arch::asm;
|
||||
|
||||
const PORT: u16 = 1337;
|
||||
const BACKLOG: i32 = 128;
|
||||
|
||||
fn main() -> Result<(), NetError> {
|
||||
let server_fd = create_socket()?;
|
||||
bind_socket(server_fd, PORT)?;
|
||||
listen_socket(server_fd, BACKLOG)?;
|
||||
const AF_INET: i32 = 2;
|
||||
const SOCK_STREAM: i32 = 1;
|
||||
const IPPROTO_IP: i32 = 0;
|
||||
|
||||
println!("TCP Server listening on port {}", PORT);
|
||||
const SYS_SOCKET: i32 = 41;
|
||||
const SYS_BIND: i32 = 49;
|
||||
const SYS_LISTEN: i32 = 50;
|
||||
const SYS_ACCEPT: i32 = 43;
|
||||
const SYS_WRITE: i32 = 1;
|
||||
const SYS_CLOSE: i32 = 3;
|
||||
const SYS_EXIT: i32 = 60;
|
||||
|
||||
#[repr(C)]
|
||||
struct SockAddrIn {
|
||||
sin_family: u16,
|
||||
sin_port: u16,
|
||||
sin_addr: u32,
|
||||
sin_zero: [u8; 8],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct SockLen {
|
||||
len: u32,
|
||||
}
|
||||
|
||||
impl SockLen {
|
||||
fn new() -> Self {
|
||||
Self { len: core::mem::size_of::<SockAddrIn>() as u32 }
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _start() {
|
||||
log_info("starting tcp server");
|
||||
|
||||
let server_fd = match create_socket() {
|
||||
Ok(fd) => {
|
||||
log_num("socket fd=", fd as i64);
|
||||
fd
|
||||
}
|
||||
Err(err) => {
|
||||
log_num("socket() failed errno=", err.errno as i64);
|
||||
exit_with(1)
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = bind_socket(server_fd, PORT) {
|
||||
log_num("bind() failed errno=", err.errno as i64);
|
||||
exit_with(1);
|
||||
}
|
||||
log_info("bound to 127.0.0.1");
|
||||
|
||||
if let Err(err) = listen_socket(server_fd, BACKLOG) {
|
||||
log_num("listen() failed errno=", err.errno as i64);
|
||||
exit_with(1);
|
||||
}
|
||||
|
||||
log_info("socket is now listening");
|
||||
|
||||
print_string("TCP Server listening on port ");
|
||||
print_u16(PORT);
|
||||
print_string("\n");
|
||||
|
||||
let mut counter: u32 = 0;
|
||||
|
||||
loop {
|
||||
match accept_client(server_fd) {
|
||||
Ok(client_fd) => {
|
||||
println!("Connect with: nc 127.0.0.1 {}", PORT);
|
||||
log_num("accepted client fd=", client_fd as i64);
|
||||
print_string("Connect with: nc 127.0.0.1 ");
|
||||
print_u16(PORT);
|
||||
print_string("\n");
|
||||
|
||||
counter += 1;
|
||||
let message = make_packet(counter);
|
||||
let _ = syscall3(SYS_WRITE, client_fd as u64, message.as_ptr() as u64, message.len as u64);
|
||||
|
||||
unsafe {
|
||||
let _ = write(
|
||||
client_fd,
|
||||
message.as_bytes().as_ptr() as *const libc::c_void,
|
||||
message.len(),
|
||||
);
|
||||
}
|
||||
|
||||
// let mut buf = [0u8; 1024];
|
||||
// loop {
|
||||
// let n = unsafe {
|
||||
// read(client_fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len())
|
||||
// };
|
||||
// if n <= 0 {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
unsafe {
|
||||
println!("Closed");
|
||||
close(client_fd);
|
||||
}
|
||||
syscall1(SYS_CLOSE, client_fd as u64);
|
||||
print_string("Closed\n");
|
||||
}
|
||||
Err(err) => {
|
||||
log_num("accept() failed errno=", err.errno as i64);
|
||||
continue;
|
||||
}
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_socket() -> Result<i32, NetError> {
|
||||
let fd = unsafe { socket(AF_INET, SOCK_STREAM, 0) };
|
||||
if fd < 0 {
|
||||
return Err(NetError::DeviceError);
|
||||
fn syscall1(num: i32, arg1: u64) -> i64 {
|
||||
let result: i64;
|
||||
unsafe {
|
||||
asm!(
|
||||
"syscall",
|
||||
in("rax") num as u64,
|
||||
in("rdi") arg1,
|
||||
lateout("rax") result,
|
||||
lateout("rcx") _,
|
||||
lateout("r11") _,
|
||||
options(nostack)
|
||||
);
|
||||
}
|
||||
Ok(fd)
|
||||
}
|
||||
|
||||
fn bind_socket(fd: i32, port: u16) -> Result<(), NetError> {
|
||||
let addr = build_sockaddr(port);
|
||||
let result = unsafe {
|
||||
bind(
|
||||
fd,
|
||||
&addr as *const sockaddr_in as *const libc::sockaddr,
|
||||
std::mem::size_of::<sockaddr_in>() as socklen_t,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
return Err(NetError::BindFailed);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn listen_socket(fd: i32, backlog: i32) -> Result<(), NetError> {
|
||||
let result = unsafe { listen(fd, backlog) };
|
||||
if result < 0 {
|
||||
return Err(NetError::BindFailed);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn accept_client(server_fd: i32) -> Result<i32, NetError> {
|
||||
let mut client_addr: sockaddr_in = unsafe { zeroed() };
|
||||
let mut client_len: socklen_t = std::mem::size_of::<sockaddr_in>() as socklen_t;
|
||||
|
||||
let client_fd = unsafe {
|
||||
accept(
|
||||
server_fd,
|
||||
&mut client_addr as *mut sockaddr_in as *mut libc::sockaddr,
|
||||
&mut client_len,
|
||||
)
|
||||
};
|
||||
|
||||
if client_fd < 0 {
|
||||
return Err(NetError::NoConnection);
|
||||
}
|
||||
Ok(client_fd)
|
||||
}
|
||||
|
||||
fn build_sockaddr(port: u16) -> sockaddr_in {
|
||||
sockaddr_in {
|
||||
sin_family: libc::AF_INET as u16,
|
||||
sin_port: port.to_be(),
|
||||
sin_addr: libc::in_addr {
|
||||
s_addr: u32::from_le_bytes([127, 0, 0, 1]),
|
||||
},
|
||||
sin_zero: [0; 8],
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum NetError {
|
||||
NoConnection,
|
||||
BindFailed,
|
||||
DeviceError,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for NetError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::NoConnection => write!(f, "No connection"),
|
||||
Self::BindFailed => write!(f, "Bind failed"),
|
||||
Self::DeviceError => write!(f, "Device error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_packet(n: u32) -> SmallStr<16> {
|
||||
let mut result = SmallStr::<16>::new();
|
||||
result.push_slice(b"Packet #");
|
||||
push_number(&mut result, n);
|
||||
result.push(b'\n');
|
||||
result
|
||||
}
|
||||
|
||||
fn push_number(s: &mut SmallStr<16>, n: u32) {
|
||||
if n == 0 {
|
||||
s.push(b'0');
|
||||
return;
|
||||
fn syscall3(num: i32, arg1: u64, arg2: u64, arg3: u64) -> i64 {
|
||||
let result: i64;
|
||||
unsafe {
|
||||
asm!(
|
||||
"syscall",
|
||||
in("rax") num as u64,
|
||||
in("rdi") arg1,
|
||||
in("rsi") arg2,
|
||||
in("rdx") arg3,
|
||||
lateout("rax") result,
|
||||
lateout("rcx") _,
|
||||
lateout("r11") _,
|
||||
options(nostack)
|
||||
);
|
||||
}
|
||||
let mut digits = [0u8; 10];
|
||||
let mut len = 0;
|
||||
let mut num = n;
|
||||
while num > 0 {
|
||||
digits[len] = b'0' + (num % 10) as u8;
|
||||
len += 1;
|
||||
num /= 10;
|
||||
result
|
||||
}
|
||||
|
||||
fn syscall6(num: i32, arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64, arg6: u64) -> i64 {
|
||||
let result: i64;
|
||||
unsafe {
|
||||
asm!(
|
||||
"syscall",
|
||||
in("rax") num as u64,
|
||||
in("rdi") arg1,
|
||||
in("rsi") arg2,
|
||||
in("rdx") arg3,
|
||||
in("r10") arg4,
|
||||
in("r8") arg5,
|
||||
in("r9") arg6,
|
||||
lateout("rax") result,
|
||||
lateout("rcx") _,
|
||||
lateout("r11") _,
|
||||
options(nostack)
|
||||
);
|
||||
}
|
||||
for i in (0..len).rev() {
|
||||
s.push(digits[i]);
|
||||
result
|
||||
}
|
||||
|
||||
fn create_socket() -> Result<i32, SysErr> {
|
||||
let fd = syscall3(SYS_SOCKET, AF_INET as u64, SOCK_STREAM as u64, IPPROTO_IP as u64);
|
||||
if fd < 0 {
|
||||
return Err(SysErr::from_ret(fd));
|
||||
}
|
||||
Ok(fd as i32)
|
||||
}
|
||||
|
||||
fn bind_socket(fd: i32, port: u16) -> Result<(), SysErr> {
|
||||
let addr = SockAddrIn {
|
||||
sin_family: AF_INET as u16,
|
||||
sin_port: port.to_be(),
|
||||
sin_addr: 0x0100007F,
|
||||
sin_zero: [0; 8],
|
||||
};
|
||||
|
||||
let result = syscall6(
|
||||
SYS_BIND,
|
||||
fd as u64,
|
||||
(&addr as *const SockAddrIn) as u64,
|
||||
core::mem::size_of::<SockAddrIn>() as u64,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
if result < 0 {
|
||||
return Err(SysErr::from_ret(result));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn listen_socket(fd: i32, backlog: i32) -> Result<(), SysErr> {
|
||||
let result = syscall2(SYS_LISTEN, fd as u64, backlog as u64);
|
||||
if result < 0 {
|
||||
return Err(SysErr::from_ret(result));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn syscall2(num: i32, arg1: u64, arg2: u64) -> i64 {
|
||||
let result: i64;
|
||||
unsafe {
|
||||
asm!(
|
||||
"syscall",
|
||||
in("rax") num as u64,
|
||||
in("rdi") arg1,
|
||||
in("rsi") arg2,
|
||||
lateout("rax") result,
|
||||
lateout("rcx") _,
|
||||
lateout("r11") _,
|
||||
options(nostack)
|
||||
);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn accept_client(server_fd: i32) -> Result<i32, SysErr> {
|
||||
let mut addr: SockAddrIn = SockAddrIn {
|
||||
sin_family: 0,
|
||||
sin_port: 0,
|
||||
sin_addr: 0,
|
||||
sin_zero: [0; 8],
|
||||
};
|
||||
let mut addr_len: SockLen = SockLen::new();
|
||||
|
||||
let client_fd = syscall6(
|
||||
SYS_ACCEPT,
|
||||
server_fd as u64,
|
||||
(&mut addr as *mut SockAddrIn) as u64,
|
||||
(&mut addr_len as *mut SockLen) as u64,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
if client_fd < 0 {
|
||||
return Err(SysErr::from_ret(client_fd));
|
||||
}
|
||||
Ok(client_fd as i32)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct SysErr {
|
||||
errno: i32,
|
||||
}
|
||||
|
||||
impl SysErr {
|
||||
fn from_ret(ret: i64) -> Self {
|
||||
Self { errno: (-ret) as i32 }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SmallStr<const N: usize> {
|
||||
data: [u8; N],
|
||||
fn exit_with(code: i32) -> ! {
|
||||
let _ = syscall1(SYS_EXIT, code as u64);
|
||||
loop {}
|
||||
}
|
||||
|
||||
fn log_info(msg: &str) {
|
||||
write_stderr("[net] ");
|
||||
write_stderr(msg);
|
||||
write_stderr("\n");
|
||||
}
|
||||
|
||||
fn log_num(prefix: &str, value: i64) {
|
||||
write_stderr("[net] ");
|
||||
write_stderr(prefix);
|
||||
print_i64_stderr(value);
|
||||
write_stderr("\n");
|
||||
}
|
||||
|
||||
fn write_stderr(s: &str) {
|
||||
let _ = syscall3(SYS_WRITE, 2, s.as_ptr() as u64, s.len() as u64);
|
||||
}
|
||||
|
||||
fn print_i64_stderr(n: i64) {
|
||||
if n < 0 {
|
||||
write_stderr("-");
|
||||
}
|
||||
print_u64_stderr(n.unsigned_abs());
|
||||
}
|
||||
|
||||
fn print_u64_stderr(mut n: u64) {
|
||||
let mut buf = [0u8; 20];
|
||||
if n == 0 {
|
||||
buf[0] = b'0';
|
||||
let _ = syscall3(SYS_WRITE, 2, buf.as_ptr() as u64, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut len = 0usize;
|
||||
while n > 0 {
|
||||
buf[len] = b'0' + (n % 10) as u8;
|
||||
len += 1;
|
||||
n /= 10;
|
||||
}
|
||||
let mut out = [0u8; 20];
|
||||
let mut i = 0usize;
|
||||
while i < len {
|
||||
out[i] = buf[len - 1 - i];
|
||||
i += 1;
|
||||
}
|
||||
let _ = syscall3(SYS_WRITE, 2, out.as_ptr() as u64, len as u64);
|
||||
}
|
||||
|
||||
fn print_string(s: &str) {
|
||||
let stdout = 1u64;
|
||||
let buf_ptr = s.as_bytes().as_ptr();
|
||||
let len = s.len() as u64;
|
||||
let _ = syscall3(SYS_WRITE, stdout, buf_ptr as u64, len);
|
||||
}
|
||||
|
||||
fn print_u16(n: u16) {
|
||||
let mut buf = [0u8; 6];
|
||||
let mut pos = 0;
|
||||
|
||||
if n == 0 {
|
||||
buf[0] = b'0';
|
||||
pos = 1;
|
||||
} else {
|
||||
let mut digits = [0u8; 5];
|
||||
let mut count = 0;
|
||||
let mut num = n;
|
||||
while num > 0 {
|
||||
digits[count] = b'0' + (num % 10) as u8;
|
||||
count += 1;
|
||||
num /= 10;
|
||||
}
|
||||
let mut i = count;
|
||||
while i > 0 {
|
||||
i -= 1;
|
||||
buf[pos] = digits[i];
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = syscall3(SYS_WRITE, 1u64, buf.as_ptr() as u64, pos as u64);
|
||||
}
|
||||
|
||||
struct PacketBuf {
|
||||
data: [u8; 32],
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<const N: usize> SmallStr<N> {
|
||||
pub const fn new() -> Self {
|
||||
impl PacketBuf {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
data: [0u8; N],
|
||||
data: [0u8; 32],
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
pub fn push(&mut self, byte: u8) {
|
||||
if self.len < N {
|
||||
|
||||
fn push(&mut self, byte: u8) {
|
||||
if self.len < 32 {
|
||||
self.data[self.len] = byte;
|
||||
self.len += 1;
|
||||
}
|
||||
}
|
||||
pub fn push_slice(&mut self, bytes: &[u8]) {
|
||||
for &byte in bytes {
|
||||
self.push(byte);
|
||||
|
||||
fn push_str(&mut self, s: &str) {
|
||||
for &b in s.as_bytes() {
|
||||
self.push(b);
|
||||
}
|
||||
}
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.data[..self.len]
|
||||
|
||||
fn push_u32(&mut self, n: u32) {
|
||||
if n == 0 {
|
||||
self.push(b'0');
|
||||
return;
|
||||
}
|
||||
let mut digits = [0u8; 10];
|
||||
let mut count = 0;
|
||||
let mut num = n;
|
||||
while num > 0 {
|
||||
digits[count] = b'0' + (num % 10) as u8;
|
||||
count += 1;
|
||||
num /= 10;
|
||||
}
|
||||
for i in (0..count).rev() {
|
||||
self.push(digits[i]);
|
||||
}
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
|
||||
fn as_ptr(&self) -> *const u8 {
|
||||
self.data.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Default for SmallStr<N> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
fn make_packet(n: u32) -> PacketBuf {
|
||||
let mut buf = PacketBuf::new();
|
||||
buf.push_str("Packet #");
|
||||
buf.push_u32(n);
|
||||
buf.push(b'\n');
|
||||
buf
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo<'_>) -> ! {
|
||||
log_info("panic");
|
||||
loop {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user