This commit is contained in:
Altuğ Bakan 2024-07-01 22:17:29 +02:00
parent 9060cbd74f
commit 2aff61927b
7 changed files with 132 additions and 53 deletions

View file

@ -17,9 +17,9 @@ jobs:
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Build
- name: Build client
run: cargo build --features client --verbose
- name: Run tests
- name: Test
run: cargo test --verbose
- name: test client
- name: Test client
run: cargo test --features client --verbose

View file

@ -43,13 +43,15 @@ tftpc --help
```
To connect the client to a tftp server running on IP address `127.0.0.1`, read-only, on port `1234` and download a file named `example.file`
```bash
tftpc example.file -i 0.0.0.0 -p 1234 -d
```
To connect the client to a tftp server running on IP address `127.0.0.1`, read-only, on port `1234` and upload a file named `example.file`
```bash
tftpc ./example.file -i 0.0.0.0 -p 1234 -u
tftpc example.file -i 0.0.0.0 -p 1234 -u
```
## License

View file

@ -51,7 +51,7 @@ impl Client {
timeout: config.timeout,
mode: config.mode,
filename: config.filename.clone(),
save_path: config.save_directory.clone(),
save_path: config.receive_directory.clone(),
})
}
@ -73,14 +73,20 @@ impl Client {
} else {
UdpSocket::bind((Ipv6Addr::UNSPECIFIED, 0))?
};
let file = self.filename.clone();
let file_name = self
.filename
.file_name()
.ok_or("Invalid filename")?
.to_str()
.ok_or("Filename is not valid UTF-8")?
.to_owned();
let size = File::open(self.filename.clone())?.metadata()?.len() as usize;
Socket::send_to(
&socket,
&Packet::Wrq {
filename: file.into_os_string().into_string().unwrap(),
filename: file_name,
mode: "octet".into(),
options: vec![
TransferOption {
@ -98,7 +104,7 @@ impl Client {
TransferOption {
option: OptionType::TransferSize,
value: size,
}
},
],
},
&self.remote_address,
@ -174,7 +180,7 @@ impl Client {
TransferOption {
option: OptionType::TransferSize,
value: 0,
}
},
],
},
&self.remote_address,

View file

@ -23,9 +23,9 @@ use std::time::Duration;
/// ```
#[derive(Debug)]
pub struct ClientConfig {
/// Local IP address of the TFTP Server. (default: 127.0.0.1)
/// Local IP address of the TFTP Client. (default: 127.0.0.1)
pub remote_ip_address: IpAddr,
/// Local Port number of the TFTP Server. (default: 69)
/// Local Port number of the TFTP Client. (default: 69)
pub port: u16,
/// Blocksize to use during transfer. (default: 512)
pub blocksize: usize,
@ -35,8 +35,8 @@ pub struct ClientConfig {
pub timeout: Duration,
/// Upload or Download a file. (default: Download)
pub mode: Mode,
/// Directory where to save downloaded files. (default: Current Working Directory)
pub save_directory: PathBuf,
/// Download directory of the TFTP Client. (default: current working directory)
pub receive_directory: PathBuf,
/// File to Upload or Download.
pub filename: PathBuf,
}
@ -50,7 +50,7 @@ impl Default for ClientConfig {
windowsize: DEFAULT_WINDOWSIZE,
timeout: DEFAULT_TIMEOUT,
mode: Mode::Download,
save_directory: Default::default(),
receive_directory: Default::default(),
filename: Default::default(),
}
}
@ -100,14 +100,14 @@ impl ClientConfig {
return Err("Missing timeout after flag".into());
}
}
"-sd" | "--save-directory" => {
"-rd" | "--receive-directory" => {
if let Some(dir_str) = args.next() {
if !Path::new(&dir_str).exists() {
return Err(format!("{dir_str} does not exist").into());
}
config.save_directory = dir_str.into();
config.receive_directory = dir_str.into();
} else {
return Err("Missing save directory after flag".into());
return Err("Missing receive directory after flag".into());
}
}
"-u" | "--upload" => {
@ -120,15 +120,17 @@ impl ClientConfig {
println!("TFTP Client\n");
println!("Usage: tftpd client <File> [OPTIONS]\n");
println!("Options:");
println!(" -i, --ip-address <IP ADDRESS>\tIp address of the server (default: 127.0.0.1)");
println!(" -p, --port <PORT>\t\tPort of the server (default: 69)");
println!(" -b, --blocksize <number>\tSets the blocksize (default: 512)");
println!(" -w, --windowsize <number>\tSets the windowsize (default: 1)");
println!(" -t, --timeout <seconds>\tSets the timeout in seconds (default: 5)");
println!(" -u, --upload\t\t\tSets the client to upload mode, Ignores all previous download flags");
println!(" -d, --download\t\tSet the client to download mode, Invalidates all previous upload flags");
println!(" -sd, --save-directory <DIRECTORY>\tSet the directory to save files when in Download Mode (default: the directory setting)");
println!(" -h, --help\t\t\tPrint help information");
println!(" -i, --ip-address <IP ADDRESS>\t\tIp address of the server (default: 127.0.0.1)");
println!(" -p, --port <PORT>\t\t\tPort of the server (default: 69)");
println!(" -b, --blocksize <number>\t\tSets the blocksize (default: 512)");
println!(" -w, --windowsize <number>\t\tSets the windowsize (default: 1)");
println!(
" -t, --timeout <seconds>\t\tSets the timeout in seconds (default: 5)"
);
println!(" -u, --upload\t\t\t\tSets the client to upload mode, Ignores all previous download flags");
println!(" -d, --download\t\t\tSet the client to download mode, Invalidates all previous upload flags");
println!(" -rd, --receive-directory <DIRECTORY>\tSet the directory to receive files when in Download mode (default: current working directory)");
println!(" -h, --help\t\t\t\tPrint help information");
process::exit(0);
}
file_name => {
@ -166,7 +168,7 @@ mod tests {
"-w",
"2",
"-t",
"4"
"4",
]
.iter()
.map(|s| s.to_string()),
@ -175,7 +177,7 @@ mod tests {
assert_eq!(config.remote_ip_address, Ipv4Addr::new(0, 0, 0, 0));
assert_eq!(config.port, 1234);
assert_eq!(config.save_directory, PathBuf::from("/"));
assert_eq!(config.receive_directory, PathBuf::from("/"));
assert_eq!(config.filename, PathBuf::from("test.file"));
assert_eq!(config.windowsize, 2);
assert_eq!(config.blocksize, 1024);
@ -183,21 +185,12 @@ mod tests {
assert_eq!(config.timeout, Duration::from_secs(4));
}
#[test]
fn parses_partial_config() {
let config = ClientConfig::new(
[
"client",
"test.file",
"-d",
"-b",
"2048",
"-p",
"2000",
]
.iter()
.map(|s| s.to_string()),
["client", "test.file", "-d", "-b", "2048", "-p", "2000"]
.iter()
.map(|s| s.to_string()),
)
.unwrap();

View file

@ -4,7 +4,7 @@ use tftpd::{Client, ClientConfig, Mode};
fn main() {
client(env::args()).unwrap_or_else(|err| {
eprintln!("{err}");
eprintln!("{err}");
})
}
@ -27,7 +27,7 @@ fn client<T: Iterator<Item = String>>(args: T) -> Result<(), Box<dyn Error>> {
);
} else {
println!(
"Starting TFTP Client, downloading {} to {}",
"Starting TFTP Client, downloading {} from {}",
config.filename.display(),
SocketAddr::new(config.remote_ip_address, config.port),
);

View file

@ -118,23 +118,20 @@ impl Config {
}
"-h" | "--help" => {
println!("TFTP Server Daemon\n");
#[cfg(feature = "client")]
println!("Usage: tftpd server [OPTIONS]\n");
#[cfg(not(feature = "client"))]
println!("Usage: tftpd [OPTIONS]\n");
println!("Options:");
println!(" -i, --ip-address <IP ADDRESS>\tSet the ip address of the server (default: 127.0.0.1)");
println!(" -i, --ip-address <IP ADDRESS>\t\tSet the ip address of the server (default: 127.0.0.1)");
println!(
" -p, --port <PORT>\t\tSet the listening port of the server (default: 69)"
" -p, --port <PORT>\t\t\tSet the listening port of the server (default: 69)"
);
println!(" -d, --directory <DIRECTORY>\tSet the serving directory (default: current working directory)");
println!(" -d, --directory <DIRECTORY>\t\tSet the serving directory (default: current working directory)");
println!(" -rd, --receive-directory <DIRECTORY>\tSet the directory to receive files to (default: the directory setting)");
println!(" -sd, --send-directory <DIRECTORY>\tSet the directory to send files from (default: the directory setting)");
println!(" -s, --single-port\t\tUse a single port for both sending and receiving (default: false)");
println!(" -r, --read-only\t\tRefuse all write requests, making the server read-only (default: false)");
println!(" --duplicate-packets <NUM>\tDuplicate all packets sent from the server (default: 0)");
println!(" --overwrite\t\t\tOverwrite existing files (default: false)");
println!(" -h, --help\t\t\tPrint help information");
println!(" -s, --single-port\t\t\tUse a single port for both sending and receiving (default: false)");
println!(" -r, --read-only\t\t\tRefuse all write requests, making the server read-only (default: false)");
println!(" --duplicate-packets <NUM>\t\tDuplicate all packets sent from the server (default: 0)");
println!(" --overwrite\t\t\t\tOverwrite existing files (default: false)");
println!(" -h, --help\t\t\t\tPrint help information");
process::exit(0);
}
"--duplicate-packets" => {

View file

@ -2,6 +2,8 @@
use std::fs::{create_dir_all, remove_dir_all};
use std::process::{Child, Command, ExitStatus};
use std::thread;
use std::time::Duration;
const SERVER_DIR: &str = "target/integration/server";
const CLIENT_DIR: &str = "target/integration/client";
@ -41,6 +43,7 @@ fn test_send() {
initialize(format!("{SERVER_DIR}/{file_name}").as_str());
let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-d", SERVER_DIR]);
thread::sleep(Duration::from_secs(1));
let mut client = CommandRunner::new(
"atftp",
&[
@ -65,6 +68,7 @@ fn test_receive() {
initialize(format!("{CLIENT_DIR}/{file_name}").as_str());
let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-d", SERVER_DIR]);
thread::sleep(Duration::from_secs(1));
let mut client = CommandRunner::new(
"atftp",
&[
@ -89,6 +93,7 @@ fn test_send_dir() {
initialize(format!("{SERVER_DIR}/{file_name}").as_str());
let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-sd", SERVER_DIR]);
thread::sleep(Duration::from_secs(1));
let mut client = CommandRunner::new(
"atftp",
&[
@ -104,6 +109,8 @@ fn test_send_dir() {
let status = client.wait();
assert!(status.success());
check_files(file_name);
}
#[test]
@ -113,6 +120,7 @@ fn test_receive_dir() {
initialize(format!("{CLIENT_DIR}/{file_name}").as_str());
let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-rd", SERVER_DIR]);
thread::sleep(Duration::from_secs(1));
let mut client = CommandRunner::new(
"atftp",
&[
@ -128,6 +136,8 @@ fn test_receive_dir() {
let status = client.wait();
assert!(status.success());
check_files(file_name);
}
#[test]
@ -140,6 +150,7 @@ fn test_send_ipv6() {
"target/debug/tftpd",
&["-i", "::1", "-p", port, "-d", SERVER_DIR],
);
thread::sleep(Duration::from_secs(1));
let mut client = CommandRunner::new(
"atftp",
&[
@ -155,6 +166,8 @@ fn test_send_ipv6() {
let status = client.wait();
assert!(status.success());
check_files(file_name);
}
#[test]
@ -167,6 +180,7 @@ fn test_receive_ipv6() {
"target/debug/tftpd",
&["-i", "::1", "-p", port, "-d", SERVER_DIR],
);
thread::sleep(Duration::from_secs(1));
let mut client = CommandRunner::new(
"atftp",
&[
@ -182,6 +196,8 @@ fn test_receive_ipv6() {
let status = client.wait();
assert!(status.success());
check_files(file_name);
}
#[test]
@ -191,6 +207,7 @@ fn test_send_single_port_options() {
initialize(format!("{SERVER_DIR}/{file_name}").as_str());
let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-d", SERVER_DIR, "-s"]);
thread::sleep(Duration::from_secs(1));
let mut client = CommandRunner::new(
"atftp",
&[
@ -208,6 +225,60 @@ fn test_send_single_port_options() {
let status = client.wait();
assert!(status.success());
check_files(file_name);
}
#[test]
fn test_client_send() {
let file_name = "client_send";
let port = "6980";
initialize(format!("{CLIENT_DIR}/{file_name}").as_str());
let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-d", SERVER_DIR]);
thread::sleep(Duration::from_secs(1));
let mut client = CommandRunner::new(
"target/debug/tftpc",
&[
format!("{CLIENT_DIR}/{file_name}").as_str(),
"-p",
port,
"-u",
],
);
let status = client.wait();
assert!(status.success());
check_files(file_name);
}
#[test]
fn test_client_receive() {
let file_name = "client_receive";
let port = "6981";
initialize(format!("{SERVER_DIR}/{file_name}").as_str());
let _server = CommandRunner::new("target/debug/tftpd", &["-p", port, "-d", SERVER_DIR]);
thread::sleep(Duration::from_secs(1));
let mut client = CommandRunner::new(
"target/debug/tftpc",
&[
file_name,
"-p",
port,
"-d",
"-rd",
format!("{CLIENT_DIR}").as_str(),
],
);
let status = client.wait();
assert!(status.success());
check_files(file_name);
}
fn initialize(file_name: &str) {
@ -235,3 +306,13 @@ fn create_file(file_name: &str) {
.wait()
.expect("error waiting for test file creation");
}
fn check_files(file_name: &str) {
let server_file = format!("{SERVER_DIR}/{file_name}");
let client_file = format!("{CLIENT_DIR}/{file_name}");
let server_content = std::fs::read(server_file).expect("error reading server file");
let client_content = std::fs::read(client_file).expect("error reading client file");
assert_eq!(server_content, client_content);
}