Improve clarity of PROTOCOL.md

This commit is contained in:
Michael Mikovsky
2026-04-23 15:36:27 -06:00
parent 6c91b40441
commit 1fe6275169
7 changed files with 1483 additions and 676 deletions
+2
View File
@@ -0,0 +1,2 @@
[unstable]
build-std = ["core"]
+7
View File
@@ -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"
+8 -10
View File
@@ -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
View File
@@ -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 {}
}