feat(client): add --port option to specify server port

Allow users to override the default TFTP port (69) when connecting
to servers running on non-standard ports. The port can be specified
via -p or --port flag.

Examples:
  tftp -p 6969 get 192.168.1.1 config.txt
  tftp --port 1069 put myserver firmware.bin

If a port is embedded in the host (host:port format), the --port
option takes precedence.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-21 13:36:05 +01:00
parent 2388401e13
commit 35cc9ee036

View File

@@ -12,6 +12,7 @@
//! put <HOST> <LOCAL_FILE> [REMOTE_FILE] Upload a file //! put <HOST> <LOCAL_FILE> [REMOTE_FILE] Upload a file
//! //!
//! Options: //! Options:
//! -p, --port <PORT> Server port (default: 69)
//! -m, --mode <MODE> Transfer mode: octet (default) or netascii //! -m, --mode <MODE> Transfer mode: octet (default) or netascii
//! -v, --verbose Enable verbose output //! -v, --verbose Enable verbose output
//! -h, --help Print help //! -h, --help Print help
@@ -23,8 +24,8 @@
//! # Download a file //! # Download a file
//! tftp get 192.168.1.1 config.txt //! tftp get 192.168.1.1 config.txt
//! //!
//! # Download to a specific local file //! # Download from a custom port
//! tftp get 192.168.1.1 config.txt local_config.txt //! tftp -p 6969 get 192.168.1.1 config.txt
//! //!
//! # Upload a file //! # Upload a file
//! tftp put 192.168.1.1 firmware.bin //! tftp put 192.168.1.1 firmware.bin
@@ -48,14 +49,15 @@ fn print_usage(program: &str) {
eprintln!(" put <HOST> <LOCAL_FILE> [REMOTE_FILE] Upload a file"); eprintln!(" put <HOST> <LOCAL_FILE> [REMOTE_FILE] Upload a file");
eprintln!(); eprintln!();
eprintln!("Options:"); eprintln!("Options:");
eprintln!(" -p, --port <PORT> Server port (default: 69)");
eprintln!(" -m, --mode <MODE> Transfer mode: octet (default) or netascii"); eprintln!(" -m, --mode <MODE> Transfer mode: octet (default) or netascii");
eprintln!(" -v, --verbose Enable verbose output"); eprintln!(" -v, --verbose Enable verbose output");
eprintln!(" -h, --help Print help"); eprintln!(" -h, --help Print help");
eprintln!(); eprintln!();
eprintln!("Examples:"); eprintln!("Examples:");
eprintln!(" {program} get 192.168.1.1 config.txt"); eprintln!(" {program} get 192.168.1.1 config.txt");
eprintln!(" {program} put 192.168.1.1 firmware.bin"); eprintln!(" {program} -p 6969 get 192.168.1.1 config.txt");
eprintln!(" {program} -m netascii get 192.168.1.1:69 readme.txt local.txt"); eprintln!(" {program} -m netascii put 192.168.1.1 readme.txt");
} }
/// Command to execute. /// Command to execute.
@@ -75,6 +77,7 @@ enum Command {
/// Parsed command-line arguments. /// Parsed command-line arguments.
struct Args { struct Args {
command: Command, command: Command,
port: Option<u16>,
mode: Mode, mode: Mode,
verbose: bool, verbose: bool,
} }
@@ -84,6 +87,7 @@ fn parse_args() -> Result<Args, String> {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
let program = &args[0]; let program = &args[0];
let mut port: Option<u16> = None;
let mut mode = Mode::Octet; let mut mode = Mode::Octet;
let mut verbose = false; let mut verbose = false;
@@ -96,6 +100,17 @@ fn parse_args() -> Result<Args, String> {
print_usage(program); print_usage(program);
std::process::exit(0); std::process::exit(0);
} }
"-p" | "--port" => {
i += 1;
if i >= args.len() {
return Err("--port requires an argument".to_string());
}
port = Some(
args[i]
.parse()
.map_err(|_| format!("invalid port: {}", args[i]))?,
);
}
"-m" | "--mode" => { "-m" | "--mode" => {
i += 1; i += 1;
if i >= args.len() { if i >= args.len() {
@@ -172,14 +187,23 @@ fn parse_args() -> Result<Args, String> {
Ok(Args { Ok(Args {
command, command,
port,
mode, mode,
verbose, verbose,
}) })
} }
/// Format host for connection (add default port if needed). /// Format host for connection.
fn format_host(host: &str) -> String { ///
if host.contains(':') { /// If a port override is provided, it is used. Otherwise, if the host already
/// contains a port (has ':'), it is used as-is. Otherwise, the default TFTP
/// port 69 is appended.
fn format_host(host: &str, port_override: Option<u16>) -> String {
if let Some(port) = port_override {
// Strip any existing port from host and use override
let host_part = host.split(':').next().unwrap_or(host);
format!("{host_part}:{port}")
} else if host.contains(':') {
host.to_string() host.to_string()
} else { } else {
format!("{host}:69") format!("{host}:69")
@@ -203,7 +227,7 @@ fn main() -> ExitCode {
remote_file, remote_file,
local_file, local_file,
} => { } => {
let addr = format_host(&host); let addr = format_host(&host, args.port);
if args.verbose { if args.verbose {
eprintln!("Getting '{remote_file}' from {addr} -> '{local_file}'"); eprintln!("Getting '{remote_file}' from {addr} -> '{local_file}'");
@@ -255,7 +279,7 @@ fn main() -> ExitCode {
local_file, local_file,
remote_file, remote_file,
} => { } => {
let addr = format_host(&host); let addr = format_host(&host, args.port);
if args.verbose { if args.verbose {
eprintln!("Putting '{local_file}' -> {addr} as '{remote_file}'"); eprintln!("Putting '{local_file}' -> {addr} as '{remote_file}'");