Skip to content

Commit

Permalink
I learned something new about Rust and traits
Browse files Browse the repository at this point in the history
Basically, if I send something to a new thread that implements the Fn trait (e.g. a
function or a closure), then it needs to implement Copy or Clone. Clippy
said "use Copy", so I went with that.

I thought "eh, it's just a function pointer anyway", but no.
Rust is a bit more complex there. And because the function needs to
implement Copy, everything else does too. Numbers work, but not Strings,
even if they're static.

So the fix was to just use Clone instead and explicitly clone the
function type (which is always gonna be a function pointer anyway, not a
struct).

I did remove Chargen and Quote of the Day as well as the ability to
change ports. I will probably re-add QotD, but I'll also need to work on
system file paths (i.e. for the config and how it's getting read) too.

The last thing that's missing is a SIGINT handle. I probably can just
shut off the program, because I don't know how I would use channels
without either the actual service of the channel communication getting
blocked.

When I'm done, I'll try another attempt at tokio-fying this application,
since I originally started this as practice for "low-level" network
stuff like this (using TcpStream and UdpSocket, I am very happy Rust and
Zig have a std-library that wraps around Winsock and POSIX sockets).
  • Loading branch information
Stridsvagn69420 committed Oct 27, 2024
1 parent 90297fe commit 616787e
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 76 deletions.
44 changes: 44 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
[package]
name = "inetdx"
description = "Rust implementation of inetd internal services"
description = "Rust implementation of inetd trivial services"
version = "0.1.0"
edition = "2021"

[dependencies]
apputils = "0.1.5"
toml = "0.8"
serde = { version = "1", features = ["derive"] }
chrono = "0.4"
chrono = "0.4"
ctrlc = { version = "3.4", features = ["termination"] }
14 changes: 3 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
# inetdx
Rust implementation of inetd trivial services

Rust implementation of inetd internal services

| Service | Port | RFC | Notes |
| -------- | ---- | ------- | ------------------------------------ |
| Echo | 7 | RFC 862 | - |
| Discard | 9 | RRC 863 | - |
| Daytime | 13 | RFC 867 | RFC 2822 timestamp |
| QotD | 17 | RFC 865 | Custom list or /etc/motd as fallback |
| Chargen | 19 | RFC 864 | - |
| Time | 37 | RFC 868 | Unix Timestamp as u64 |
| Hostname | 42 | - | cat /etc/hostname over TCP+UDP |
<!--TODO: Add more description, e.g. list of network services-->
This is supposed to be a rather simple project, but I'm open for any feature requests, so just [open an issue](https://github.com/Stridsvagn69420/inetdx/issues/new).
Empty file removed src/chargen.rs
Empty file.
13 changes: 6 additions & 7 deletions src/daytime.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
use std::net::TcpStream;
use std::net::IpAddr;
use std::io::Write;
use std::io;
use chrono::Local;
use crate::shared::{tcp_server, udp_server, BUFFER_SIZE_UDP};

pub(crate) const NAME: &str = "Daytime";
pub(crate) const PORT: u16 = 13;
const NAME: &str = "Daytime";
const PORT: u16 = 13;

/// Daytime TCP-Server
pub(crate) fn daytime_tcp(addr: IpAddr, port: u16) -> io::Result<()> {
tcp_server(addr, port, NAME, move |stream: &mut TcpStream| {
pub(crate) fn daytime_tcp(addr: IpAddr) -> io::Result<()> {
tcp_server(addr, PORT, NAME, move |mut stream| {
// Write RFC 2822 timestamp
let time = timestamp();
stream.write_all(time.as_bytes())?;
Expand All @@ -19,8 +18,8 @@ pub(crate) fn daytime_tcp(addr: IpAddr, port: u16) -> io::Result<()> {
}

/// Daytime UDP-Server
pub(crate) fn daytime_udp(addr: IpAddr, port: u16) -> io::Result<()> {
udp_server(addr, port, NAME, |socket| {
pub(crate) fn daytime_udp(addr: IpAddr) -> io::Result<()> {
udp_server(addr, PORT, NAME, |socket| {
// Accept Datagram
let mut buf = [0; BUFFER_SIZE_UDP];
let (_, peer) = socket.recv_from(&mut buf)?;
Expand Down
20 changes: 7 additions & 13 deletions src/discard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ use std::io::Read;
use std::io;
use crate::shared::{tcp_server, udp_server, BUFFER_SIZE_TCP, BUFFER_SIZE_UDP};

pub(crate) const NAME: &str = "Discard";
pub(crate) const PORT: u16 = 9;
const NAME: &str = "Discard";
const PORT: u16 = 9;

/// Discard TCP-Server
pub(crate) fn discard_tcp(addr: IpAddr, port: u16) -> io::Result<()> {
tcp_server(addr, port, NAME, |stream| {
pub(crate) fn discard_tcp(addr: IpAddr) -> io::Result<()> {
tcp_server(addr, PORT, NAME, |mut stream| {
loop {
// Read client data and ignore it like a boss
let mut buf = [0; BUFFER_SIZE_TCP];
Expand All @@ -22,17 +22,11 @@ pub(crate) fn discard_tcp(addr: IpAddr, port: u16) -> io::Result<()> {
}

/// Discard UDP-Server
pub(crate) fn discard_udp(addr: IpAddr, port: u16) -> io::Result<()> {
udp_server(addr, port, NAME, |socket| {
pub(crate) fn discard_udp(addr: IpAddr) -> io::Result<()> {
udp_server(addr, PORT, NAME, |socket| {
// Accept Datagram
let mut buf = [0; BUFFER_SIZE_UDP];
let (n, peer) = socket.recv_from(&mut buf)?;

// Debug Code
if cfg!(debug_assertions) {
let data = String::from_utf8_lossy(&buf[..n]);
println!("DEBUG [{NAME}] Received {n} Bytes from {peer}: {data}");
}
let _ = socket.recv_from(&mut buf)?;
Ok(())
})
}
19 changes: 7 additions & 12 deletions src/echo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,35 @@ use std::io::{Read, Write};
use std::io;
use crate::shared::{tcp_server, udp_server, BUFFER_SIZE_TCP};

pub(crate) const NAME: &str = "Echo";
pub(crate) const PORT: u16 = 7;
const NAME: &str = "Echo";
const PORT: u16 = 7;

const BUFFER_SIZE_UDP: usize = 65507;

/// Echo TCP-Server
pub(crate) fn echo_tcp(addr: IpAddr, port: u16) -> io::Result<()> {
tcp_server(addr, port, NAME, |stream| {
pub(crate) fn echo_tcp(addr: IpAddr) -> io::Result<()> {
tcp_server(addr, PORT, NAME, |mut stream| {
loop {
// Read client data and write it back
let mut buf = [0; BUFFER_SIZE_TCP];
let n = stream.read(&mut buf)?;
if n == 0 {
break;
}

stream.write_all(&buf[..n])?;
}
Ok(())
})
}

/// Echo UDP-Server
pub(crate) fn echo_udp(addr: IpAddr, port: u16) -> io::Result<()> {
udp_server(addr, port, NAME, |socket| {
pub(crate) fn echo_udp(addr: IpAddr) -> io::Result<()> {
udp_server(addr, PORT, NAME, |socket| {
// Accept Datagram
let mut buf = [0; BUFFER_SIZE_UDP];
let (n, peer) = socket.recv_from(&mut buf)?;

// Debug Code
if cfg!(debug_assertions) {
let data = String::from_utf8_lossy(&buf[..n]);
println!("DEBUG [{NAME}] Received {n} Bytes from {peer}: {data}");
}

// Return if data was sent
if n > 0 {
socket.send_to(&buf[..n], peer)?;
Expand Down
21 changes: 12 additions & 9 deletions src/hostname.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::net::TcpStream;
use std::net::IpAddr;
use std::io::Write;
use std::io;
Expand All @@ -10,28 +9,32 @@ use std::fs;
#[cfg(not(target_family = "unix"))]
use std::env;

pub(crate) const NAME: &str = "Hostname";
pub(crate) const PORT: u16 = 42;
const NAME: &str = "Hostname";
const PORT: u16 = 42;

/// Hostname TCP-Server
pub(crate) fn hostname_tcp(addr: IpAddr, port: u16) -> io::Result<()> {
tcp_server(addr, port, NAME, move |stream: &mut TcpStream| {
pub(crate) fn hostname_tcp(addr: IpAddr) -> io::Result<()> {
// Read hostname once
let nodename = hostname()?;

tcp_server(addr, PORT, NAME, move |mut stream| {
// Write Hostname
let nodename = hostname()?;
stream.write_all(nodename.as_bytes())?;
Ok(())
})
}

/// Hostname UDP-Server
pub(crate) fn hostname_udp(addr: IpAddr, port: u16) -> io::Result<()> {
udp_server(addr, port, NAME, |socket| {
pub(crate) fn hostname_udp(addr: IpAddr) -> io::Result<()> {
// Read hostname once
let nodename = hostname()?;

udp_server(addr, PORT, NAME, move |socket| {
// Accept Datagram
let mut buf = [0; BUFFER_SIZE_UDP];
let (_, peer) = socket.recv_from(&mut buf)?;

// Return Hostname
let nodename = hostname()?;
socket.send_to(nodename.as_bytes(), peer)?;
Ok(())
})
Expand Down
6 changes: 3 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::process::ExitCode;
use std::sync::mpsc;
use ctrlc;

mod config;
mod shared;
mod echo;
mod discard;
mod daytime;
mod qotd;
mod chargen;
mod time;
mod hostname;

Expand All @@ -17,7 +17,7 @@ fn main() -> ExitCode {
println!();

// Configure example Server
if let Err(err) = echo::echo_udp(cfg.into(), echo::PORT) {
if let Err(err) = echo::echo_udp(cfg.into()) {
println!("Could not start server ({}): {err}", err.kind());
ExitCode::FAILURE
} else {
Expand Down
Empty file removed src/qotd.rs
Empty file.
26 changes: 14 additions & 12 deletions src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ pub(crate) const BUFFER_SIZE_TCP: usize = 4096;
/// The default size for each UDP server's read buffer.
///
/// This buffer is at the MTU for non-loopback LANs, because non-loopback networks usually have an MTU of around 1500 Bytes.
/// If the service does read directly from loopback, then use **65507** (not 65535 due to practical limitations);
/// If the service actually reads user input, use something like 65507. 65535 works too, but there is some overhead with IP packets,
/// so you're wasting a few bytes.
///
/// The reason why buffer size matter is that [every datagram is a one-time read](UdpSocket::recv_from)
/// and you cannot get the actual size without messing with heap memory.
Expand All @@ -32,9 +33,9 @@ pub(crate) const BUFFER_SIZE_UDP: usize = 1500;
/// - `addr`: The [IPv6 address](Ipv6Addr) to bind to.
/// - `port`: The port to bind to.
/// - `f`: The service handle function.
pub(crate) fn tcp_server<F>(addr: IpAddr, port: u16, name: &str, mut f: F) -> io::Result<()>
pub(crate) fn tcp_server<F>(addr: IpAddr, port: u16, name: &'static str, f: F) -> io::Result<()>
where
F: FnMut(&mut TcpStream) -> io::Result<()> + Send + Copy + 'static
F: Fn(TcpStream) -> io::Result<()> + Send + Clone + 'static
{
// Create TCP Listener
let listener = match TcpListener::bind((addr, port)) {
Expand All @@ -50,21 +51,22 @@ where
// Listener Loop
loop {
id += 1;
let mut connection = match listener.accept() {
let (conn, _) = match listener.accept() {
Ok(conn) => {
println!("[Port {port}] ({id}) Connected to {}", conn.1);
println!("[{name}] ({id}) Connected to {}", conn.1);
conn
},
Err(err) => {
println!("[Port {port}] ({id}) TCP-Handshake failed ({}): {err}", err.kind());
println!("[{name}] ({id}) TCP-Handshake failed ({}): {err}", err.kind());
continue;
},
};

let fclone = f.clone();
thread::spawn(move || {
match f(&mut connection.0) {
Ok(_) => println!("[Port {port}] ({id}) Stream closed."),
Err(err) => println!("[Port {port}] ({id}) {}: {err}", err.kind()),
match fclone(conn) {
Ok(_) => println!("[{name}] ({id}) Stream closed."),
Err(err) => println!("[{name}] ({id}) {}: {err}", err.kind()),
}
});
}
Expand All @@ -78,21 +80,21 @@ where
/// - `f`: The service handle function. Due to technical difficulties, the function needs to log seperately.
pub(crate) fn udp_server<F>(addr: IpAddr, port: u16, name: &str, mut f: F) -> io::Result<()>
where
F: FnMut(&mut UdpSocket) -> io::Result<()> + Send + Copy + 'static
F: FnMut(&mut UdpSocket) -> io::Result<()>
{
// Create UDP Socket
let mut socket = match UdpSocket::bind((addr, port)) {
Err(err) => return Err(err),
Ok(sock) => {
println!("{name} UDP-Server started: {addr}:{port}");
println!("[{name}] UDP-Server started: {addr}:{port}");
sock
}
};

// Constantly iterate over service function
loop {
if let Err(err) = f(&mut socket) {
println!("[Port {port}] {}: {err}", err.kind())
println!("[{name}] {}: {err}", err.kind())
}
}
}
Loading

0 comments on commit 616787e

Please sign in to comment.