Working network test

This commit is contained in:
Michael Mikovsky
2026-04-22 13:12:25 -06:00
parent cd301dea67
commit d0ca53f5c7
9 changed files with 536 additions and 52 deletions
Generated
+7
View File
@@ -444,6 +444,13 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "no-alloc-network-test"
version = "0.1.0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
+1 -1
View File
@@ -27,7 +27,7 @@ members = [
# Libraries # Libraries
"ush-obfuscate", "ush-obfuscate",
"base62", "base62", "no-alloc-network-test",
] ]
resolver = "2" resolver = "2"
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "no-alloc-network-test"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
include.workspace = true
[dependencies]
libc = "0.2"
[lints]
workspace = true
+185
View File
@@ -0,0 +1,185 @@
//! # TCP Network Stack using Raw Syscalls
//!
//! A TCP server using raw syscalls via libc - no std::net, no smoltcp.
//!
//! ## Usage
//! ```bash
//! cargo run
//! nc 127.0.0.1 1337
//! ```
use libc::{accept, bind, listen, socket, sockaddr_in, socklen_t, read, write, close, AF_INET, SOCK_STREAM};
use core::mem::zeroed;
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)?;
println!("TCP Server listening on port {}", PORT);
println!("Connect with: nc 127.0.0.1 {}", PORT);
let mut counter: u32 = 0;
loop {
match accept_client(server_fd) {
Ok(client_fd) => {
counter += 1;
let message = make_packet(counter);
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 {
close(client_fd);
}
}
Err(_) => continue,
}
}
}
fn create_socket() -> Result<i32, NetError> {
let fd = unsafe {
socket(AF_INET, SOCK_STREAM, 0)
};
if fd < 0 {
return Err(NetError::DeviceError);
}
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;
}
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;
}
for i in (0..len).rev() {
s.push(digits[i]);
}
}
pub struct SmallStr<const N: usize> {
data: [u8; N],
len: usize,
}
impl<const N: usize> SmallStr<N> {
pub const fn new() -> Self {
Self { data: [0u8; N], len: 0 }
}
pub fn push(&mut self, byte: u8) {
if self.len < N {
self.data[self.len] = byte;
self.len += 1;
}
}
pub fn push_slice(&mut self, bytes: &[u8]) {
for &byte in bytes {
self.push(byte);
}
}
pub fn as_bytes(&self) -> &[u8] {
&self.data[..self.len]
}
pub fn len(&self) -> usize {
self.len
}
}
impl<const N: usize> Default for SmallStr<N> {
fn default() -> Self {
Self::new()
}
}
+77 -10
View File
@@ -114,9 +114,17 @@ impl Cli {
/// ///
/// # Returns /// # Returns
/// List of child node names /// List of child node names
pub fn list_nodes(&self, path: Option<&str>) -> Result<Vec<String>, String> { pub fn list_nodes(&mut self, path: Option<&str>) -> Result<Vec<String>, String> {
let path = path.unwrap_or(&self.current_path); let path = path.map(|p| p.to_string()).unwrap_or_else(|| self.current_path.clone());
self.tree.list_nodes(path) if self.is_connected() && self.is_remote_path(&path) {
let response = self.send_request(&path, &TreeRequest::ListNodes {})?;
match response {
TreeResponse::NodeList { names } => Ok(names),
_ => Err("unexpected response type".to_string()),
}
} else {
self.tree.list_nodes(&path)
}
} }
/// List endpoints at a path. /// List endpoints at a path.
@@ -124,21 +132,80 @@ impl Cli {
/// # Arguments /// # Arguments
/// * `path` - Optional path (defaults to current path) /// * `path` - Optional path (defaults to current path)
pub fn list_endpoints( pub fn list_endpoints(
&self, &mut self,
path: Option<&str>, path: Option<&str>,
) -> Result<Vec<crate::protocol::EndpointInfo>, String> { ) -> Result<Vec<crate::protocol::EndpointInfo>, String> {
let path = path.unwrap_or(&self.current_path); let path = path.map(|p| p.to_string()).unwrap_or_else(|| self.current_path.clone());
self.tree.list_endpoints(path) if self.is_connected() && self.is_remote_path(&path) {
let response = self.send_request(&path, &TreeRequest::ListEndpoints {})?;
match response {
TreeResponse::EndpointList { endpoints } => Ok(endpoints),
_ => Err("unexpected response type".to_string()),
}
} else {
self.tree.list_endpoints(&path)
}
} }
/// List all leaf paths. /// List all leaf paths.
pub fn list_leaves(&self) -> Vec<String> { pub fn list_leaves(&mut self) -> Vec<String> {
self.tree.list_leaves() if self.is_connected() {
let response = match self.send_request("/", &TreeRequest::ListLeaves {}) {
Ok(r) => r,
Err(_) => return self.tree.list_leaves(),
};
match response {
TreeResponse::LeafList { leaves } => leaves.into_iter().map(|p| self.normalize_path(&p)).collect(),
_ => self.tree.list_leaves(),
}
} else {
self.tree.list_leaves()
}
} }
/// Get info about a node. /// Get info about a node.
pub fn get_info(&self, path: &str) -> Result<crate::protocol::NodeInfo, String> { pub fn get_info(&mut self, path: &str) -> Result<crate::protocol::NodeInfo, String> {
self.tree.get_info(path) let path_owned = path.to_string();
if self.is_connected() && self.is_remote_path(&path_owned) {
let response = self.send_request(&path_owned, &TreeRequest::GetInfo { path: path_owned.clone() })?;
match response {
TreeResponse::NodeInfo { info } => Ok(info),
_ => Err("unexpected response type".to_string()),
}
} else {
self.tree.get_info(path)
}
}
/// Check if a path is a remote path (not local).
///
/// A path is remote if it's not the base_path assigned to this client.
fn is_remote_path(&self, path: &str) -> bool {
path != self.base_path && !path.starts_with(&self.base_path)
}
/// Normalize a path by removing double slashes.
///
/// # Example
/// ```
/// assert_eq!(normalize_path("//shell"), "/shell");
/// assert_eq!(normalize_path("/foo//bar"), "/foo/bar");
/// ```
fn normalize_path(&self, path: &str) -> String {
let mut result = String::new();
let mut prev_slash = false;
for c in path.chars() {
if c == '/' {
if !prev_slash {
result.push(c);
}
prev_slash = true;
} else {
result.push(c);
prev_slash = false;
}
}
result
} }
/// Execute a command locally on the tree. /// Execute a command locally on the tree.
+2
View File
@@ -1,7 +1,9 @@
//! # Leaves Module //! # Leaves Module
pub mod proxy;
pub mod shell; pub mod shell;
pub mod tty; pub mod tty;
pub use proxy::ProxyEndpoint;
pub use shell::RemoteShell; pub use shell::RemoteShell;
pub use tty::TTY; pub use tty::TTY;
+106
View File
@@ -0,0 +1,106 @@
//! # Proxy Endpoint
//!
//! This module provides a proxy endpoint that routes to child nodes for ls/info operations.
#[allow(unused_imports)]
use crate::protocol::{TreeRequest, TreeResponse, EndpointType};
#[allow(unused_imports)]
use crate::tree::Endpoint;
use std::string::String;
/// A proxy endpoint that routes to children for ls/info operations.
///
/// This endpoint is used at the root ("/") to allow traversal to child endpoints
/// like /shell and /tty when there's no direct handler at the root.
#[derive(Debug)]
pub struct ProxyEndpoint {
name: String,
#[allow(dead_code)]
tree: Option<Box<crate::tree::Tree>>,
}
impl ProxyEndpoint {
/// Create a new proxy endpoint.
///
/// # Arguments
/// * `name` - The name of this endpoint (typically "proxy")
/// * `tree` - Optional tree to proxy to
///
/// # Example
/// ```
/// let proxy = ProxyEndpoint::new("proxy", None);
/// ```
#[allow(dead_code)]
pub fn new(name: &str, _tree: Option<Box<crate::tree::Tree>>) -> Self {
Self {
name: name.to_string(),
tree: None,
}
}
/// Create a proxy endpoint with an empty tree.
pub fn new_empty(name: &str) -> Self {
Self {
name: name.to_string(),
tree: None,
}
}
}
impl crate::tree::Endpoint for ProxyEndpoint {
fn handle_request(&mut self, request: &TreeRequest, _src_path: &str) -> Result<TreeResponse, String> {
match request {
TreeRequest::ListNodes { .. } => {
if let Some(ref tree) = self.tree {
let names = tree.list_nodes("/").unwrap_or_default();
Ok(TreeResponse::NodeList { names })
} else {
Ok(TreeResponse::NodeList { names: vec![] })
}
}
TreeRequest::ListEndpoints { .. } => {
if let Some(ref tree) = self.tree {
let endpoints = tree.list_endpoints("/").unwrap_or_default();
Ok(TreeResponse::EndpointList { endpoints })
} else {
Ok(TreeResponse::EndpointList { endpoints: vec![] })
}
}
TreeRequest::ListLeaves { .. } => {
if let Some(ref tree) = self.tree {
let leaves = tree.list_leaves();
Ok(TreeResponse::LeafList { leaves })
} else {
Ok(TreeResponse::LeafList { leaves: vec![] })
}
}
TreeRequest::GetInfo { path } => {
if let Some(ref tree) = self.tree {
let info = tree.get_info(path)?;
Ok(TreeResponse::NodeInfo { info })
} else {
Err("no tree available".to_string())
}
}
_ => Err("unsupported request on proxy".to_string()),
}
}
fn on_stream_open(&mut self, stream_id: u16, _src_path: &str) -> Option<u16> {
Some(stream_id)
}
fn on_stream_data(&mut self, _stream_id: u16, _data: &[u8]) -> bool {
false
}
fn on_stream_close(&mut self, _stream_id: u16) {}
fn endpoint_type(&self) -> EndpointType {
EndpointType::Proxy
}
fn name(&self) -> &str {
&self.name
}
}
+32 -6
View File
@@ -3,12 +3,16 @@
//! This module provides the server functionality for handling incoming connections. //! This module provides the server functionality for handling incoming connections.
use crate::protocol::{ use crate::protocol::{
FrameHeader, FrameType, TreeRequest, TreeResponse, TcpTransport, Transport, FrameHeader, FrameType, Handshake, TreeRequest, TreeResponse, TcpTransport, Transport,
make_response, make_handshake_ack, make_response, make_handshake_ack,
}; };
use crate::tree::Tree; use crate::tree::Tree;
use crate::leaves::{RemoteShell, TTY}; use crate::leaves::{ProxyEndpoint, RemoteShell, TTY};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicU32, Ordering};
/// Global client counter for assigning unique base paths.
static CLIENT_COUNT: AtomicU32 = AtomicU32::new(0);
/// Default listening address for the server. /// Default listening address for the server.
/// ///
@@ -38,6 +42,7 @@ pub fn run_server(addr: &str) -> ! {
let tree = Arc::new(Mutex::new(Tree::new())); let tree = Arc::new(Mutex::new(Tree::new()));
{ {
let mut tree = tree.lock().unwrap(); let mut tree = tree.lock().unwrap();
tree.add_endpoint("/", Box::new(ProxyEndpoint::new_empty("proxy")));
tree.add_endpoint("/shell", Box::new(RemoteShell::new("shell"))); tree.add_endpoint("/shell", Box::new(RemoteShell::new("shell")));
tree.add_endpoint("/tty", Box::new(TTY::new("tty"))); tree.add_endpoint("/tty", Box::new(TTY::new("tty")));
} }
@@ -70,7 +75,7 @@ pub fn run_server(addr: &str) -> ! {
/// * `transport` - The TCP transport for the connection /// * `transport` - The TCP transport for the connection
/// * `tree` - Shared access to the tree /// * `tree` - Shared access to the tree
pub fn handle_connection(mut transport: TcpTransport, tree: Arc<Mutex<Tree>>) { pub fn handle_connection(mut transport: TcpTransport, tree: Arc<Mutex<Tree>>) {
let (header, _payload) = match transport.recv_frame() { let (header, payload) = match transport.recv_frame() {
Ok(h) => h, Ok(h) => h,
Err(e) => { Err(e) => {
log::error!("recv error: {:?}", e); log::error!("recv error: {:?}", e);
@@ -85,7 +90,28 @@ pub fn handle_connection(mut transport: TcpTransport, tree: Arc<Mutex<Tree>>) {
log::info!("Client connected"); log::info!("Client connected");
let (ack_header, ack_payload) = make_handshake_ack(true, "/client"); let base_path = if payload.is_empty() {
let client_num = CLIENT_COUNT.fetch_add(1, Ordering::SeqCst);
format!("/client_{}", client_num)
} else {
let handshake = match Handshake::from_bytes(&payload) {
Ok(h) => h,
Err(e) => {
log::error!("handshake parse error: {}", e);
return;
}
};
let client_num = CLIENT_COUNT.fetch_add(1, Ordering::SeqCst);
if handshake.registered_paths.is_empty() {
format!("/client_{}", client_num)
} else {
handshake.registered_paths.first().cloned().unwrap_or_else(|| {
format!("/client_{}", client_num)
})
}
};
let (ack_header, ack_payload) = make_handshake_ack(true, &base_path);
transport.send_frame(&ack_header, Some(&ack_payload)).expect("send failed"); transport.send_frame(&ack_header, Some(&ack_payload)).expect("send failed");
loop { loop {
@@ -168,11 +194,11 @@ pub fn handle_frame(
let response = match request { let response = match request {
TreeRequest::ListNodes {} => { TreeRequest::ListNodes {} => {
let names = tree.list_nodes(dst_path).unwrap_or_default(); let names = tree.list_nodes_at(dst_path);
TreeResponse::NodeList { names } TreeResponse::NodeList { names }
} }
TreeRequest::ListEndpoints {} => { TreeRequest::ListEndpoints {} => {
let endpoints = tree.list_endpoints(dst_path).unwrap_or_default(); let endpoints = tree.list_endpoints_at(dst_path);
TreeResponse::EndpointList { endpoints } TreeResponse::EndpointList { endpoints }
} }
TreeRequest::ListLeaves {} => { TreeRequest::ListLeaves {} => {
+109 -32
View File
@@ -63,6 +63,52 @@ impl Node {
self.children.keys().cloned().collect() self.children.keys().cloned().collect()
} }
/// List child nodes at a given path - traverses directly without find_handler
///
/// Works even without endpoint at the path (like Linux directories).
///
/// # Arguments
/// * `path` - The path to list children at (e.g., "/" or "/shell")
///
/// # Returns
/// List of child node names, or empty list if path not found
pub fn list_nodes_at(&self, path: &str) -> Vec<String> {
let segments: Vec<String> = path
.split('/')
.filter(|s| !s.is_empty())
.map(String::from)
.collect();
let mut current = self;
for seg in &segments {
if let Some(child) = current.children.get(seg) {
current = child;
} else {
return vec![];
}
}
current.child_names()
}
/// List endpoints at a given path - traverses directly without find_handler
///
/// Works even without endpoint at the path (like Linux directories).
pub fn list_endpoints_at(&self, path: &str) -> Vec<EndpointInfo> {
let segments: Vec<String> = path
.split('/')
.filter(|s| !s.is_empty())
.map(String::from)
.collect();
let mut current = self;
for seg in &segments {
if let Some(child) = current.children.get(seg) {
current = child;
} else {
return vec![];
}
}
current.endpoint_names()
}
/// Get all endpoints at this node and in children /// Get all endpoints at this node and in children
pub fn endpoint_names(&self) -> Vec<EndpointInfo> { pub fn endpoint_names(&self) -> Vec<EndpointInfo> {
let mut endpoints = Vec::new(); let mut endpoints = Vec::new();
@@ -99,7 +145,11 @@ impl Node {
for (name, child) in &self.children { for (name, child) in &self.children {
let mut child_leaves = child.leaf_paths(); let mut child_leaves = child.leaf_paths();
for path in &mut child_leaves { for path in &mut child_leaves {
*path = format!("{}/{}", self.path, name); *path = if self.path == "/" {
format!("/{}", name)
} else {
format!("{}/{}", self.path, name)
};
paths.push(path.clone()); paths.push(path.clone());
} }
} }
@@ -121,7 +171,7 @@ impl Node {
/// Tree structure for routing - contains the root node. /// Tree structure for routing - contains the root node.
#[allow(dead_code)] #[allow(dead_code)]
pub struct Tree { pub struct Tree {
root: Node, pub root: Node,
} }
impl fmt::Debug for Tree { impl fmt::Debug for Tree {
@@ -186,7 +236,7 @@ impl Tree {
/// Returns the endpoint and the matched path /// Returns the endpoint and the matched path
pub fn find_handler(&self, path: &str) -> Option<(Arc<Mutex<Box<dyn Endpoint>>>, &str)> { pub fn find_handler(&self, path: &str) -> Option<(Arc<Mutex<Box<dyn Endpoint>>>, &str)> {
if path == "/" { if path == "/" {
return self.root.endpoint.as_ref().map(|e| (e.clone(), "")); return self.root.endpoint.as_ref().map(|e| (e.clone(), "/"));
} }
let segments = path_segments(path); let segments = path_segments(path);
@@ -207,45 +257,72 @@ impl Tree {
current.endpoint.as_ref().map(|e| (e.clone(), handler_path)) current.endpoint.as_ref().map(|e| (e.clone(), handler_path))
} }
/// List child nodes at a given path /// List child nodes at a given path using direct tree traversal.
///
/// Unlike `find_handler()`, this works even without an endpoint at the path,
/// making "/" and other directories traversable like Linux directories.
///
/// # Example
/// ```
/// let tree = Tree::new();
/// tree.add_endpoint("/shell", Box::new(RemoteShell::new("shell")));
/// let names = tree.list_nodes("/").unwrap(); // ["shell"]
/// ```
pub fn list_nodes(&self, path: &str) -> Result<Vec<String>, String> { pub fn list_nodes(&self, path: &str) -> Result<Vec<String>, String> {
let (_, matched_path) = self.find_handler(path) // Use direct traversal - works without endpoint
.ok_or_else(|| format!("path not found: {}", path))?; let names = self.root.list_nodes_at(path);
Ok(names)
let segments = path_segments(matched_path);
let mut current = &self.root;
for segment in &segments {
if let Some(child) = current.children.get(segment) {
current = child;
}
}
Ok(current.child_names())
} }
/// List all endpoints at a given path /// List all endpoints at a given path.
///
/// Works even without endpoint at the path.
pub fn list_endpoints(&self, path: &str) -> Result<Vec<EndpointInfo>, String> { pub fn list_endpoints(&self, path: &str) -> Result<Vec<EndpointInfo>, String> {
let (_, matched_path) = self.find_handler(path) let endpoints = self.root.list_endpoints_at(path);
.ok_or_else(|| format!("path not found: {}", path))?; Ok(endpoints)
let segments = path_segments(matched_path);
let mut current = &self.root;
for segment in &segments {
if let Some(child) = current.children.get(segment) {
current = child;
}
}
Ok(current.endpoint_names())
} }
/// List all leaf paths in the tree /// List all leaf paths in the tree.
pub fn list_leaves(&self) -> Vec<String> { pub fn list_leaves(&self) -> Vec<String> {
self.root.leaf_paths() self.root.leaf_paths()
} }
/// List child nodes at a given path - traverses directly without find_handler
pub fn list_nodes_at(&self, path: &str) -> Vec<String> {
let segments: Vec<String> = path
.split('/')
.filter(|s| !s.is_empty())
.map(String::from)
.collect();
let mut current = &self.root;
for seg in &segments {
if let Some(child) = current.children.get(seg) {
current = child;
} else {
return vec![];
}
}
current.child_names()
}
/// List endpoints at a given path - traverses directly without find_handler
pub fn list_endpoints_at(&self, path: &str) -> Vec<EndpointInfo> {
let segments: Vec<String> = path
.split('/')
.filter(|s| !s.is_empty())
.map(String::from)
.collect();
let mut current = &self.root;
for seg in &segments {
if let Some(child) = current.children.get(seg) {
current = child;
} else {
return vec![];
}
}
current.endpoint_names()
}
/// Get information about a node at the given path /// Get information about a node at the given path
pub fn get_info(&self, path: &str) -> Result<NodeInfo, String> { pub fn get_info(&self, path: &str) -> Result<NodeInfo, String> {
let segments = path_segments(path); let segments = path_segments(path);