Finish implementation

This commit is contained in:
altugbakan 2023-03-11 17:23:18 +03:00
parent c345a5b531
commit 0df85e156d
7 changed files with 404 additions and 180 deletions

2
Cargo.lock generated
View file

@ -4,4 +4,4 @@ version = 3
[[package]]
name = "tftpd"
version = "0.0.0"
version = "0.1.0"

View file

@ -1,6 +1,6 @@
[package]
name = "tftpd"
version = "0.0.0"
version = "0.1.0"
authors = ["Altuğ Bakan <mail@alt.ug>"]
edition = "2021"
description = "TFTP Server Daemon implemented in Rust"

View file

@ -3,14 +3,14 @@ use tftpd::{Config, Server};
fn main() {
let config = Config::new(env::args()).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {}", err);
eprintln!("Problem parsing arguments: {err}");
process::exit(1)
});
let server = Server::new(&config).unwrap_or_else(|err| {
eprintln!(
"Problem creating server on {}:{}: {}",
config.ip_address, config.port, err
"Problem creating server on {}:{}: {err}",
config.ip_address, config.port
);
process::exit(1)
});

View file

@ -7,17 +7,28 @@ use crate::packet::{ErrorCode, Opcode, Packet, TransferOption};
pub struct Message;
const MAX_REQUEST_PACKET_SIZE: usize = 512;
impl Message {
pub fn send_data(socket: &UdpSocket, data: &[u8]) -> Result<(), Box<dyn Error>> {
let buf = [&Opcode::Data.as_bytes()[..], data].concat();
pub fn send_data(
socket: &UdpSocket,
block_number: u16,
data: &[u8],
) -> Result<(), Box<dyn Error>> {
let buf = [
&Opcode::Data.as_bytes()[..],
&block_number.to_be_bytes(),
data,
]
.concat();
socket.send(&buf)?;
Ok(())
}
pub fn send_ack(socket: &UdpSocket, block: u16) -> Result<(), Box<dyn Error>> {
let buf = [Opcode::Ack.as_bytes(), block.to_be_bytes()].concat();
pub fn send_ack(socket: &UdpSocket, block_number: u16) -> Result<(), Box<dyn Error>> {
let buf = [Opcode::Ack.as_bytes(), block_number.to_be_bytes()].concat();
socket.send(&buf)?;
@ -29,16 +40,21 @@ impl Message {
code: ErrorCode,
msg: &str,
) -> Result<(), Box<dyn Error>> {
socket.send(&get_error_buf(code, msg))?;
socket.send(&build_error_buf(code, msg))?;
Ok(())
}
pub fn send_error_to(socket: &UdpSocket, to: &SocketAddr, code: ErrorCode, msg: &str) {
eprintln!("{msg}");
if socket.send_to(&get_error_buf(code, msg), to).is_err() {
pub fn send_error_to<'a>(
socket: &UdpSocket,
to: &SocketAddr,
code: ErrorCode,
msg: &'a str,
) -> Result<(), Box<dyn Error>> {
if socket.send_to(&build_error_buf(code, msg), to).is_err() {
eprintln!("could not send an error message");
}
Err(msg.into())
}
pub fn send_oack(
@ -56,19 +72,32 @@ impl Message {
Ok(())
}
pub fn receive_ack(socket: &UdpSocket) -> Result<u16, Box<dyn Error>> {
let mut buf = [0; 4];
socket.recv(&mut buf)?;
pub fn recv(socket: &UdpSocket) -> Result<Packet, Box<dyn Error>> {
let mut buf = [0; MAX_REQUEST_PACKET_SIZE];
let number_of_bytes = socket.recv(&mut buf)?;
let packet = Packet::deserialize(&buf[..number_of_bytes])?;
if let Ok(Packet::Ack(block)) = Packet::deserialize(&buf) {
Ok(block)
} else {
Err("invalid ack".into())
}
Ok(packet)
}
pub fn recv_data(socket: &UdpSocket, size: usize) -> Result<Packet, Box<dyn Error>> {
let mut buf = vec![0; size + 4];
let number_of_bytes = socket.recv(&mut buf)?;
let packet = Packet::deserialize(&buf[..number_of_bytes])?;
Ok(packet)
}
pub fn recv_from(socket: &UdpSocket) -> Result<(Packet, SocketAddr), Box<dyn Error>> {
let mut buf = [0; MAX_REQUEST_PACKET_SIZE];
let (number_of_bytes, from) = socket.recv_from(&mut buf)?;
let packet = Packet::deserialize(&buf[..number_of_bytes])?;
Ok((packet, from))
}
}
fn get_error_buf(code: ErrorCode, msg: &str) -> Vec<u8> {
fn build_error_buf(code: ErrorCode, msg: &str) -> Vec<u8> {
[
&Opcode::Error.as_bytes()[..],
&code.as_bytes()[..],

View file

@ -1,7 +1,7 @@
use crate::Convert;
use std::error::Error;
use std::{error::Error, fmt};
pub enum Packet<'a> {
pub enum Packet {
Rrq {
filename: String,
mode: String,
@ -14,7 +14,7 @@ pub enum Packet<'a> {
},
Data {
block_num: u16,
data: &'a [u8],
data: Vec<u8>,
},
Ack(u16),
Error {
@ -23,9 +23,9 @@ pub enum Packet<'a> {
},
}
impl<'a> Packet<'a> {
pub fn deserialize(buf: &'a [u8]) -> Result<Packet, Box<dyn Error>> {
let opcode = Opcode::from_u16(Convert::to_u16(&buf[0..1])?)?;
impl Packet {
pub fn deserialize(buf: &[u8]) -> Result<Packet, Box<dyn Error>> {
let opcode = Opcode::from_u16(Convert::to_u16(&buf[0..=1])?)?;
match opcode {
Opcode::Rrq | Opcode::Wrq => parse_rq(buf, opcode),
@ -66,7 +66,7 @@ impl Opcode {
}
}
#[derive(Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TransferOption {
pub option: OptionType,
pub value: usize,
@ -84,7 +84,7 @@ impl TransferOption {
}
}
#[derive(Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum OptionType {
BlockSize,
TransferSize,
@ -100,10 +100,6 @@ impl OptionType {
}
}
fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
fn from_str(value: &str) -> Result<Self, &'static str> {
match value {
"blksize" => Ok(OptionType::BlockSize),
@ -147,6 +143,21 @@ impl ErrorCode {
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ErrorCode::NotDefined => write!(f, "Not Defined"),
ErrorCode::FileNotFound => write!(f, "File Not Found"),
ErrorCode::AccessViolation => write!(f, "Access Violation"),
ErrorCode::DiskFull => write!(f, "Disk Full"),
ErrorCode::IllegalOperation => write!(f, "Illegal Operation"),
ErrorCode::UnknownId => write!(f, "Unknown ID"),
ErrorCode::FileExists => write!(f, "File Exists"),
ErrorCode::NoSuchUser => write!(f, "No Such User"),
}
}
}
fn parse_rq(buf: &[u8], opcode: Opcode) -> Result<Packet, Box<dyn Error>> {
let mut options = vec![];
let filename: String;
@ -188,7 +199,7 @@ fn parse_rq(buf: &[u8], opcode: Opcode) -> Result<Packet, Box<dyn Error>> {
fn parse_data(buf: &[u8]) -> Result<Packet, Box<dyn Error>> {
Ok(Packet::Data {
block_num: Convert::to_u16(&buf[2..])?,
data: &buf[4..],
data: buf[4..].to_vec(),
})
}
@ -239,11 +250,11 @@ mod tests {
&[0x00],
&"octet".as_bytes(),
&[0x00],
&OptionType::TransferSize.as_bytes(),
&OptionType::TransferSize.as_str().as_bytes(),
&[0x00],
&"0".as_bytes(),
&[0x00],
&OptionType::Timeout.as_bytes(),
&OptionType::Timeout.as_str().as_bytes(),
&[0x00],
&"5".as_bytes(),
&[0x00],
@ -311,11 +322,11 @@ mod tests {
&[0x00],
&"octet".as_bytes(),
&[0x00],
&OptionType::TransferSize.as_bytes(),
&OptionType::TransferSize.as_str().as_bytes(),
&[0x00],
&"12341234".as_bytes(),
&[0x00],
&OptionType::BlockSize.as_bytes(),
&OptionType::BlockSize.as_str().as_bytes(),
&[0x00],
&"1024".as_bytes(),
&[0x00],

View file

@ -2,9 +2,7 @@ use crate::packet::{ErrorCode, Packet, TransferOption};
use crate::{Config, Message, Worker};
use std::error::Error;
use std::net::{SocketAddr, UdpSocket};
use std::path::{Path, PathBuf};
const MAX_REQUEST_PACKET_SIZE: usize = 512;
use std::path::PathBuf;
pub struct Server {
socket: UdpSocket,
@ -25,63 +23,86 @@ impl Server {
pub fn listen(&self) {
loop {
let mut buf = [0; MAX_REQUEST_PACKET_SIZE];
if let Ok((number_of_bytes, from)) = self.socket.recv_from(&mut buf) {
if let Ok(packet) = Packet::deserialize(&buf[..number_of_bytes]) {
self.handle_packet(&packet, &from)
}
if let Ok((packet, from)) = Message::recv_from(&self.socket) {
match packet {
Packet::Rrq {
filename,
mut options,
..
} => match self.handle_rrq(filename.clone(), &mut options, &from) {
Ok(_) => {
println!("Sending {filename} to {from}");
}
Err(err) => eprintln!("{err}"),
},
Packet::Wrq {
filename,
mut options,
..
} => match self.handle_wrq(filename.clone(), &mut options, &from) {
Ok(_) => {
println!("Receiving {filename} from {from}");
}
Err(err) => eprintln!("{err}"),
},
_ => {
Message::send_error_to(
&self.socket,
&from,
ErrorCode::IllegalOperation,
"invalid request",
)
.unwrap_or_else(|err| eprintln!("{err}"));
}
};
}
}
}
fn handle_packet(&self, packet: &Packet, from: &SocketAddr) {
match &packet {
Packet::Rrq {
filename, options, ..
} => self.validate_rrq(filename, options, from),
Packet::Wrq {
filename, options, ..
} => self.validate_wrq(filename, options, from),
_ => {
Message::send_error_to(
&self.socket,
from,
ErrorCode::IllegalOperation,
"invalid request",
);
}
}
}
fn validate_rrq(&self, filename: &String, options: &Vec<TransferOption>, to: &SocketAddr) {
match self.check_file_exists(&Path::new(&filename)) {
fn handle_rrq(
&self,
filename: String,
options: &mut Vec<TransferOption>,
to: &SocketAddr,
) -> Result<(), Box<dyn Error>> {
match check_file_exists(&get_full_path(&filename, &self.directory), &self.directory) {
ErrorCode::FileNotFound => {
Message::send_error_to(
return Message::send_error_to(
&self.socket,
to,
ErrorCode::FileNotFound,
"requested file does not exist",
"file does not exist",
);
}
ErrorCode::AccessViolation => {
Message::send_error_to(
return Message::send_error_to(
&self.socket,
to,
ErrorCode::AccessViolation,
"requested file is not in the directory",
"file access violation",
);
}
ErrorCode::FileExists => self
.handle_rrq(filename, options, to)
.unwrap_or_else(|err| eprintln!("could not handle read request: {err}")),
ErrorCode::FileExists => Worker::send(
self.socket.local_addr().unwrap(),
*to,
filename,
options.to_vec(),
),
_ => {}
}
Ok(())
}
fn validate_wrq(&self, filename: &String, options: &Vec<TransferOption>, to: &SocketAddr) {
match self.check_file_exists(&Path::new(&filename)) {
fn handle_wrq(
&self,
filename: String,
options: &mut Vec<TransferOption>,
to: &SocketAddr,
) -> Result<(), Box<dyn Error>> {
match check_file_exists(&get_full_path(&filename, &self.directory), &self.directory) {
ErrorCode::FileExists => {
Message::send_error_to(
return Message::send_error_to(
&self.socket,
to,
ErrorCode::FileExists,
@ -89,53 +110,85 @@ impl Server {
);
}
ErrorCode::AccessViolation => {
Message::send_error_to(
return Message::send_error_to(
&self.socket,
to,
ErrorCode::AccessViolation,
"requested file is not in the directory",
"file access violation",
);
}
ErrorCode::FileNotFound => self
.handle_wrq(filename, options, to)
.unwrap_or_else(|err| eprintln!("could not handle write request: {err}")),
ErrorCode::FileNotFound => Worker::receive(
self.socket.local_addr().unwrap(),
*to,
filename,
options.to_vec(),
),
_ => {}
};
}
fn handle_rrq(
&self,
filename: &String,
options: &Vec<TransferOption>,
to: &SocketAddr,
) -> Result<(), Box<dyn Error>> {
let mut worker = Worker::new(&self.socket.local_addr().unwrap(), to)?;
worker.send_file(Path::new(&filename), options)?;
Ok(())
}
fn handle_wrq(
&self,
filename: &String,
options: &Vec<TransferOption>,
to: &SocketAddr,
) -> Result<(), Box<dyn Error>> {
let mut worker = Worker::new(&self.socket.local_addr().unwrap(), to)?;
worker.receive_file(Path::new(&filename), options)?;
Ok(())
}
fn check_file_exists(&self, file: &Path) -> ErrorCode {
if !file.ancestors().any(|a| a == &self.directory) {
return ErrorCode::AccessViolation;
}
if !file.exists() {
return ErrorCode::FileNotFound;
}
ErrorCode::FileExists
}
}
fn check_file_exists(file: &PathBuf, directory: &PathBuf) -> ErrorCode {
if !validate_file_path(file, directory) {
return ErrorCode::AccessViolation;
}
if !file.exists() {
return ErrorCode::FileNotFound;
}
ErrorCode::FileExists
}
fn validate_file_path(file: &PathBuf, directory: &PathBuf) -> bool {
!file.to_str().unwrap().contains("..") && file.ancestors().any(|a| a == directory)
}
fn get_full_path(filename: &str, directory: &PathBuf) -> PathBuf {
let mut file = directory.clone();
file.push(PathBuf::from(filename));
file
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gets_full_path() {
assert_eq!(
get_full_path("test.txt", &PathBuf::from("/dir/test")),
PathBuf::from("/dir/test/test.txt")
);
assert_eq!(
get_full_path("some_dir/test.txt", &PathBuf::from("/dir/test")),
PathBuf::from("/dir/test/some_dir/test.txt")
);
}
#[test]
fn validates_file_path() {
assert!(validate_file_path(
&PathBuf::from("/dir/test/file"),
&PathBuf::from("/dir/test")
));
assert!(!validate_file_path(
&PathBuf::from("/system/data.txt"),
&PathBuf::from("/dir/test")
));
assert!(!validate_file_path(
&PathBuf::from("~/some_data.txt"),
&PathBuf::from("/dir/test")
));
assert!(!validate_file_path(
&PathBuf::from("/dir/test/../file"),
&PathBuf::from("/dir/test")
));
}
}

View file

@ -1,111 +1,242 @@
use std::{
error::Error,
fs::File,
io::Read,
io::{Read, Write},
net::{SocketAddr, UdpSocket},
path::Path,
thread,
time::Duration,
};
use crate::{
packet::{OptionType, TransferOption},
packet::{ErrorCode, OptionType, Packet, TransferOption},
Message,
};
pub struct Worker {
socket: UdpSocket,
pub struct Worker;
pub struct WorkerOptions {
blk_size: usize,
t_size: usize,
timeout: usize,
timeout: u64,
}
#[derive(PartialEq, Eq)]
enum WorkType {
Receive,
Send(u64),
}
const MAX_RETRIES: u32 = 6;
const DEFAULT_TIMEOUT_SECS: u64 = 5;
const DEFAULT_BLOCK_SIZE: usize = 512;
impl Worker {
pub fn new(addr: &SocketAddr, remote: &SocketAddr) -> Result<Worker, Box<dyn Error>> {
let socket = UdpSocket::bind(SocketAddr::from((addr.ip(), 0)))?;
socket.connect(remote)?;
Ok(Worker {
socket,
blk_size: 512,
t_size: 0,
timeout: 5,
})
pub fn send(
addr: SocketAddr,
remote: SocketAddr,
filename: String,
mut options: Vec<TransferOption>,
) {
thread::spawn(move || {
let mut handle_send = || -> Result<(), Box<dyn Error>> {
let socket = setup_socket(&addr, &remote)?;
let work_type = WorkType::Send(Path::new(&filename).metadata().unwrap().len());
let worker_options = parse_options(&mut options, &work_type)?;
send_oack(&socket, &options, &work_type)?;
send_file(&socket, &worker_options, &filename, &mut options)?;
Ok(())
};
if let Err(err) = handle_send() {
eprintln!("{err}");
}
});
}
pub fn send_file(
&mut self,
file: &Path,
options: &Vec<TransferOption>,
) -> Result<(), Box<dyn Error>> {
let mut file = File::open(file).unwrap();
pub fn receive(
addr: SocketAddr,
remote: SocketAddr,
filename: String,
mut options: Vec<TransferOption>,
) {
thread::spawn(move || {
let mut handle_receive = || -> Result<(), Box<dyn Error>> {
let socket = setup_socket(&addr, &remote)?;
let work_type = WorkType::Receive;
let worker_options = parse_options(&mut options, &work_type)?;
send_oack(&socket, &options, &work_type)?;
receive_file(&socket, &worker_options, &filename, &mut options)?;
self.parse_options(options, Some(&file));
Message::send_oack(&self.socket, options)?;
Ok(())
};
self.socket
.set_write_timeout(Some(Duration::from_secs(self.timeout as u64)))?;
if let Err(err) = handle_receive() {
eprintln!("{err}");
}
});
}
}
fn send_file(
socket: &UdpSocket,
worker_options: &WorkerOptions,
filename: &String,
options: &mut Vec<TransferOption>,
) -> Result<(), Box<dyn Error>> {
let mut file = File::open(filename).unwrap();
parse_options(options, &WorkType::Send(file.metadata().unwrap().len()))?;
let mut block_number = 1;
loop {
let mut chunk = vec![0; worker_options.blk_size];
let size = file.read(&mut chunk)?;
let mut retry_cnt = 0;
loop {
let mut chunk = Vec::with_capacity(self.blk_size);
let size = file
.by_ref()
.take(self.blk_size as u64)
.read_to_end(&mut chunk)?;
Message::send_data(socket, block_number, &chunk[..size])?;
loop {
if Message::send_data(&self.socket, &chunk).is_err() {
return Err(format!("failed to send data").into());
match Message::recv(socket) {
Ok(Packet::Ack(received_block_number)) => {
if received_block_number == block_number {
block_number = block_number.wrapping_add(1);
break;
}
}
if let Ok(block) = Message::receive_ack(&self.socket) {
todo!("handle block number");
} else {
Ok(Packet::Error { code, msg }) => {
return Err(format!("received error code {code}, with message {msg}").into());
}
_ => {
retry_cnt += 1;
if retry_cnt == MAX_RETRIES {
return Err(format!("transfer timed out after {MAX_RETRIES} tries").into());
}
}
}
if size < self.blk_size {
break;
};
}
Ok(())
if size < worker_options.blk_size {
break;
};
}
pub fn receive_file(
&mut self,
file: &Path,
options: &Vec<TransferOption>,
) -> Result<(), Box<dyn Error>> {
let mut file = File::open(file).unwrap();
println!("Sent {filename} to {}", socket.peer_addr().unwrap());
Ok(())
}
self.parse_options(options, Some(&file));
Message::send_oack(&self.socket, options)?;
fn receive_file(
socket: &UdpSocket,
worker_options: &WorkerOptions,
filename: &String,
options: &mut Vec<TransferOption>,
) -> Result<(), Box<dyn Error>> {
let mut file = File::create(filename).unwrap();
todo!("file receiving");
parse_options(options, &WorkType::Receive)?;
Ok(())
}
let mut block_number: u16 = 0;
loop {
let size;
fn parse_options(&mut self, options: &Vec<TransferOption>, file: Option<&File>) {
for option in options {
let TransferOption { option, value } = option;
match option {
OptionType::BlockSize => self.blk_size = *value,
OptionType::TransferSize => {
self.t_size = match file {
Some(file) => file.metadata().unwrap().len() as usize,
None => *value,
let mut retry_cnt = 0;
loop {
match Message::recv_data(socket, worker_options.blk_size) {
Ok(Packet::Data {
block_num: received_block_number,
data,
}) => {
if received_block_number == block_number.wrapping_add(1) {
block_number = received_block_number;
file.write(&data)?;
size = data.len();
break;
}
}
OptionType::Timeout => self.timeout = *value,
Ok(Packet::Error { code, msg }) => {
return Err(format!("received error code {code}: {msg}").into());
}
Err(err) => {
retry_cnt += 1;
if retry_cnt == MAX_RETRIES {
return Err(
format!("transfer timed out after {MAX_RETRIES} tries: {err}").into(),
);
}
}
_ => {}
}
}
Message::send_ack(socket, block_number)?;
if size < worker_options.blk_size {
break;
};
}
println!("Received {filename} from {}", socket.peer_addr().unwrap());
Ok(())
}
fn setup_socket(addr: &SocketAddr, remote: &SocketAddr) -> Result<UdpSocket, Box<dyn Error>> {
let socket = UdpSocket::bind(SocketAddr::from((addr.ip(), 0)))?;
socket.connect(remote)?;
socket.set_read_timeout(Some(Duration::from_secs(DEFAULT_TIMEOUT_SECS)))?;
socket.set_write_timeout(Some(Duration::from_secs(DEFAULT_TIMEOUT_SECS)))?;
Ok(socket)
}
fn parse_options(
options: &mut Vec<TransferOption>,
work_type: &WorkType,
) -> Result<WorkerOptions, Box<dyn Error>> {
let mut worker_options = WorkerOptions {
blk_size: DEFAULT_BLOCK_SIZE,
t_size: 0,
timeout: DEFAULT_TIMEOUT_SECS,
};
for option in &mut *options {
let TransferOption { option, value } = option;
match option {
OptionType::BlockSize => worker_options.blk_size = *value,
OptionType::TransferSize => match work_type {
WorkType::Send(size) => {
*value = *size as usize;
}
WorkType::Receive => {
worker_options.t_size = *value;
}
},
OptionType::Timeout => {
if *value == 0 {
return Err("invalid timeout value".into());
}
worker_options.timeout = *value as u64;
}
}
}
Ok(worker_options)
}
fn send_oack(
socket: &UdpSocket,
options: &Vec<TransferOption>,
work_type: &WorkType,
) -> Result<(), Box<dyn Error>> {
if options.len() > 0 {
Message::send_oack(socket, options)?;
if let Packet::Ack(received_block_number) = Message::recv(socket)? {
if received_block_number != 0 {
Message::send_error(socket, ErrorCode::IllegalOperation, "invalid oack response")?;
}
}
} else if *work_type == WorkType::Receive {
Message::send_ack(socket, 0)?
}
Ok(())
}