├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── tftpd-dir.rs └── tftpd-targz.rs ├── rfcs ├── rfc1350.txt ├── rfc2347.txt ├── rfc2348.txt └── rfc2349.txt ├── rustfmt.toml └── src ├── error.rs ├── lib.rs ├── packet.rs ├── parse.rs ├── server ├── builder.rs ├── handler.rs ├── handlers │ ├── dir.rs │ └── mod.rs ├── mod.rs ├── read_req.rs ├── server.rs └── write_req.rs ├── tests ├── external_client.rs ├── handlers.rs ├── mod.rs ├── packet.rs ├── random_file.rs └── rrq.rs └── utils.rs /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test-linux: 6 | name: Tests 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, windows-latest, macos-latest] 11 | timeout-minutes: 20 12 | env: 13 | RUST_BACKTRACE: 1 14 | steps: 15 | - name: Install atftp (Linux) 16 | if: matrix.os == 'ubuntu-latest' 17 | run: | 18 | sudo apt-get update 19 | sudo apt-get install -y atftp 20 | - name: Install toolchain 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | profile: minimal 24 | toolchain: stable 25 | override: true 26 | - name: Checkout sources 27 | uses: actions/checkout@v1 28 | - name: Run tests 29 | uses: actions-rs/cargo@v1 30 | with: 31 | command: test 32 | args: --all-features 33 | - name: Run long tests 34 | uses: actions-rs/cargo@v1 35 | with: 36 | command: test 37 | args: '--all-features -- tests:: --ignored' 38 | lints: 39 | name: Lints 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Install toolchain 43 | uses: actions-rs/toolchain@v1 44 | with: 45 | profile: minimal 46 | toolchain: stable 47 | override: true 48 | components: rustfmt, clippy 49 | - uses: actions/checkout@v1 50 | - name: Run cargo fmt 51 | uses: actions-rs/cargo@v1 52 | with: 53 | command: fmt 54 | args: --all -- --check 55 | - name: Run cargo clippy 56 | uses: actions-rs/cargo@v1 57 | with: 58 | command: clippy 59 | args: -- -D clippy::all 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [unreleased] 9 | 10 | ### Changed 11 | 12 | - Remove `num-traits` dependency 13 | - Update all dependencies 14 | - Use `tokio` in examples 15 | 16 | ## [0.3.6] - 2022-12-16 17 | 18 | ### Changed 19 | 20 | - Send responses and errors from the bind address [#14](https://github.com/oblique/async-tftp-rs/pull/14) 21 | - Upgrade dependencies 22 | 23 | ## [0.3.5] - 2021-01-28 24 | 25 | ### Changed 26 | 27 | - Upgrade to `bytes` 1.0 28 | - Migrate from `async-mutex` to `async-lock` 29 | - Upgdate other dependencies 30 | 31 | ## [0.3.4] - 2020-12-13 32 | 33 | ### Changed 34 | 35 | - Upgrade to `bytes` 0.6.0 36 | - Upgrade to `nom` 6.0.1 37 | - Upgrade other dependencies 38 | - Use async-executor instead of FuturesUnordered 39 | 40 | ## [0.3.3] - 2020-09-14 41 | 42 | ### Changed 43 | 44 | - Upgrade to the v1 of [smol] building blocks. 45 | 46 | ## [0.3.2] - 2020-08-31 47 | 48 | ### Changed 49 | 50 | - Remove `once_cell` from dependencies. 51 | - Upgrade to new smol building blocks. 52 | 53 | ## [0.3.1] - 2020-08-22 54 | 55 | ### Improve 56 | 57 | - Rewrite `tftpd-targz.rs` example with `async-tar` and `async-compression` 58 | crates. 59 | - Use only `alloc` feature flag for `futures-util`. 60 | 61 | ## [0.3.0] - 2020-08-17 62 | 63 | ### Added 64 | 65 | - async-tftp is now runtime/executor agnostic thanks to [smol] building 66 | blocks. You can even run it with a simple `block_on`. 67 | - Added an example on how you can serve files from a tar.gz. 68 | - Added `TftpServerBuilder::std_socket`. 69 | 70 | ### Changed 71 | 72 | - Because `use-tokio` feature flag is removed, `Handler` now only accepts 73 | `futures_io::AsyncRead` and `futures_io::AsyncWrite`. 74 | - `TftpServerBuilder::socket` now accepts `async_io::Async`. 75 | 76 | ### Removed 77 | 78 | - Removed `use-async-std` feature flag. 79 | - Removed `use-tokio` feature flag. 80 | - Removed `async_tftp::log::set_log_level`. 81 | 82 | ## [0.2.0] - 2020-02-08 83 | 84 | ### Added 85 | 86 | - Handle write requests. 87 | - Added `TftpServerBuilder::with_dir_wo` that handles only write 88 | requests. 89 | - Added `TftpServerBuilder::with_dir_rw` that handles read and write 90 | requests. 91 | - Added `use-async-std` feature flag, to enable async-std 1.0 integration (default). 92 | - Added `use-tokio` feature flag, to enable Tokio 0.2 integration. 93 | 94 | ### Changed 95 | 96 | - `Handler` trait needs a `Writer` associated type. 97 | - `DirRoHandler` is renamed to `DirHandler`. 98 | - `DirHandler::new` now requires initialization flags. 99 | 100 | ## [0.1.3] - 2019-11-20 101 | 102 | ### Added 103 | 104 | - Minor improvements for read request. 105 | - Added tests for non-default block size. 106 | 107 | ## [0.1.2] - 2019-11-20 108 | 109 | ### Added 110 | 111 | - You can now set the maximum send retries of a data block via 112 | `TftpServerBuilder::max_send_retries`. Default is 100 retries. 113 | - You can now produce a serve request failure on the first `read` 114 | 115 | ## [0.1.1] - 2019-11-17 116 | 117 | ### Fixed 118 | 119 | - Improve test cases. 120 | 121 | ## [0.1.0] - 2019-11-17 122 | 123 | [First release](https://docs.rs/async-tftp/0.1.0) 124 | 125 | 126 | [unreleased]: https://github.com/oblique/async-tftp-rs/compare/0.3.6...HEAD 127 | [0.3.6]: https://github.com/oblique/async-tftp-rs/compare/0.3.5...0.3.6 128 | [0.3.5]: https://github.com/oblique/async-tftp-rs/compare/0.3.4...0.3.5 129 | [0.3.4]: https://github.com/oblique/async-tftp-rs/compare/0.3.3...0.3.4 130 | [0.3.3]: https://github.com/oblique/async-tftp-rs/compare/0.3.2...0.3.3 131 | [0.3.2]: https://github.com/oblique/async-tftp-rs/compare/0.3.1...0.3.2 132 | [0.3.1]: https://github.com/oblique/async-tftp-rs/compare/0.3.0...0.3.1 133 | [0.3.0]: https://github.com/oblique/async-tftp-rs/compare/0.2.0...0.3.0 134 | [0.2.0]: https://github.com/oblique/async-tftp-rs/compare/0.1.3...0.2.0 135 | [0.1.3]: https://github.com/oblique/async-tftp-rs/compare/0.1.2...0.1.3 136 | [0.1.2]: https://github.com/oblique/async-tftp-rs/compare/0.1.1...0.1.2 137 | [0.1.1]: https://github.com/oblique/async-tftp-rs/compare/0.1.0...0.1.1 138 | [0.1.0]: https://github.com/oblique/async-tftp-rs/releases/tag/0.1.0 139 | 140 | [smol]: https://github.com/stjepang/smol 141 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-tftp" 3 | version = "0.3.6" 4 | authors = ["oblique "] 5 | edition = "2021" 6 | rust-version = "1.75" 7 | license = "MIT" 8 | readme = "README.md" 9 | 10 | description = "Executor agnostic async TFTP implementation" 11 | categories = ["network-programming"] 12 | keywords = ["tftp", "tftpd", "async-std", "tokio", "smol"] 13 | exclude = [".github", "rfcs"] 14 | repository = "https://github.com/oblique/async-tftp-rs" 15 | 16 | [dependencies] 17 | bytes = "1.5.0" 18 | log = "0.4.20" 19 | thiserror = "1.0.48" 20 | 21 | async-executor = "1.5.1" 22 | async-io = "1.13.0" 23 | async-lock = "2.8.0" 24 | blocking = "1.3.1" 25 | futures-lite = "1.13.0" 26 | 27 | [dev-dependencies] 28 | anyhow = "1.0.75" 29 | async-channel = "1.9.0" 30 | fern = "0.6.2" 31 | md5 = "0.7.0" 32 | rand = { version = "0.8.5", features = ["small_rng"] } 33 | structopt = "0.3.26" 34 | tempfile = "3.8.0" 35 | tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros"] } 36 | 37 | # deps for tftpd-targz.rs 38 | async-compression = { version = "0.4.3", features = ["gzip", "futures-io"] } 39 | async-std = { version = "1.12.0", features = ["unstable"] } 40 | async-tar = "0.4.2" 41 | 42 | [features] 43 | external-client-tests = [] 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 oblique 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-tftp 2 | 3 | [![license][license badge]][license] 4 | [![crates.io][crate badge]][crate] 5 | [![docs][docs badge]][docs] 6 | 7 | Executor agnostic async TFTP implementation, written with [smol] 8 | building blocks. Currently it implements only server side. 9 | 10 | The following RFCs are implemented: 11 | 12 | * [RFC 1350] - The TFTP Protocol (Revision 2). 13 | * [RFC 2347] - TFTP Option Extension. 14 | * [RFC 2348] - TFTP Blocksize Option. 15 | * [RFC 2349] - TFTP Timeout Interval and Transfer Size Options. 16 | 17 | Features: 18 | 19 | * Async implementation. 20 | * Works with any runtime/executor. 21 | * Serve read (RRQ) and write (WRQ) requests. 22 | * Unlimited transfer file size (block number roll-over). 23 | * You can set non-standard reply [`timeout`]. This is useful for faster 24 | file transfer in unstable environments. 25 | * You can set [block size limit]. This is useful if you are accessing 26 | client through a VPN. 27 | * You can implement your own [`Handler`] for more advance cases than 28 | just serving a directory. Check [`tftpd-targz.rs`] for an example. 29 | 30 | # Example 31 | 32 | ```rust 33 | use async_tftp::server::TftpServerBuilder; 34 | use async_tftp::Result; 35 | 36 | #[tokio::main] // or any other runtime/executor 37 | async fn main() -> Result<()> { 38 | let tftpd = TftpServerBuilder::with_dir_ro(".")?.build().await?; 39 | tftpd.serve().await?; 40 | Ok(()) 41 | } 42 | ``` 43 | 44 | Add in `Cargo.toml`: 45 | 46 | ```toml 47 | [dependencies] 48 | async-tftp = "0.3" 49 | # or any other runtime/executor 50 | tokio = { version = "1", features = ["rt-multi-thread", "macros"] } 51 | ``` 52 | 53 | ## Running examples with cargo 54 | 55 | There are some examples included with this crate. 56 | You can run them from a source checkout with cargo: 57 | 58 | ```bash 59 | $ cargo run --example tftpd-dir 60 | TFTP directory: ... 61 | Listening on: 0.0.0.0:6969 62 | ^C 63 | 64 | $ cargo run --example tftpd-targz 65 | Listening on: 0.0.0.0:6969 66 | ^C 67 | ``` 68 | 69 | # License 70 | 71 | [MIT][license] 72 | 73 | [smol]: https://crates.io/crates/smol 74 | 75 | [license]: LICENSE 76 | [license badge]: https://img.shields.io/github/license/oblique/async-tftp-rs 77 | [crate]: https://crates.io/crates/async-tftp 78 | [crate badge]: https://img.shields.io/crates/v/async-tftp 79 | [docs]: https://docs.rs/async-tftp 80 | [docs badge]: https://docs.rs/async-tftp/badge.svg 81 | 82 | [`timeout`]: https://docs.rs/async-tftp/latest/async_tftp/server/struct.TftpServerBuilder.html#method.timeout 83 | [block size limit]: https://docs.rs/async-tftp/latest/async_tftp/server/struct.TftpServerBuilder.html#method.block_size_limit 84 | [`Handler`]: https://docs.rs/async-tftp/latest/async_tftp/server/trait.Handler.html 85 | [`tftpd-targz.rs`]: https://github.com/oblique/async-tftp-rs/blob/master/examples/tftpd-targz.rs 86 | 87 | [RFC 1350]: https://tools.ietf.org/html/rfc1350 88 | [RFC 2347]: https://tools.ietf.org/html/rfc2347 89 | [RFC 2348]: https://tools.ietf.org/html/rfc2348 90 | [RFC 2349]: https://tools.ietf.org/html/rfc2349 91 | -------------------------------------------------------------------------------- /examples/tftpd-dir.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use async_tftp::server::TftpServerBuilder; 3 | 4 | #[tokio::main] 5 | async fn main() -> Result<()> { 6 | fern::Dispatch::new() 7 | .level(log::LevelFilter::Info) 8 | .level_for("async_tftp", log::LevelFilter::Trace) 9 | .chain(std::io::stdout()) 10 | .apply() 11 | .expect("Failed to initialize logger"); 12 | 13 | let tftpd = TftpServerBuilder::with_dir_ro(".")? 14 | .bind("0.0.0.0:6969".parse().unwrap()) 15 | // Workaround to handle cases where client is behind VPN 16 | .block_size_limit(1024) 17 | .build() 18 | .await?; 19 | 20 | log::info!("Listening on: {}", tftpd.listen_addr()?); 21 | tftpd.serve().await?; 22 | 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /examples/tftpd-targz.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use structopt::StructOpt; 3 | 4 | use async_compression::futures::bufread::GzipDecoder; 5 | use async_std::fs::File; 6 | use async_std::io::{BufReader, Sink}; 7 | use async_std::path::{Path, PathBuf}; 8 | use async_std::stream::StreamExt; 9 | use async_std::task::block_on; 10 | use async_tar::{Archive, Entry}; 11 | use async_tftp::packet; 12 | use async_tftp::server::{Handler, TftpServerBuilder}; 13 | use std::net::SocketAddr; 14 | 15 | struct TftpdTarGzHandler { 16 | archive_path: PathBuf, 17 | } 18 | 19 | impl TftpdTarGzHandler { 20 | fn new(path: impl AsRef) -> Self { 21 | TftpdTarGzHandler { 22 | archive_path: path.as_ref().to_owned(), 23 | } 24 | } 25 | } 26 | 27 | // Sometimes paths within archives start with `/` or `./`, strip both. 28 | fn strip_path_prefixes(path: &Path) -> &Path { 29 | path.strip_prefix("/").or_else(|_| path.strip_prefix("./")).unwrap_or(path) 30 | } 31 | 32 | impl Handler for TftpdTarGzHandler { 33 | type Reader = Entry>>>; 34 | type Writer = Sink; 35 | 36 | async fn read_req_open( 37 | &mut self, 38 | _client: &SocketAddr, 39 | path: &std::path::Path, 40 | ) -> Result<(Self::Reader, Option), packet::Error> { 41 | let req_path = strip_path_prefixes(path.into()).to_owned(); 42 | 43 | let file = File::open(self.archive_path.clone()).await?; 44 | let archive = Archive::new(GzipDecoder::new(BufReader::new(file))); 45 | 46 | let mut entries = archive.entries()?; 47 | 48 | while let Some(Ok(entry)) = entries.next().await { 49 | if entry 50 | .path() 51 | .map(|p| strip_path_prefixes(&p) == req_path) 52 | .unwrap_or(false) 53 | { 54 | // We manage to find the entry. 55 | 56 | // Check if it is a regular file. 57 | if entry.header().entry_type() != async_tar::EntryType::Regular 58 | { 59 | break; 60 | } 61 | 62 | return Ok((entry, None)); 63 | } 64 | } 65 | 66 | Err(packet::Error::FileNotFound) 67 | } 68 | 69 | async fn write_req_open( 70 | &mut self, 71 | _client: &SocketAddr, 72 | _path: &std::path::Path, 73 | _size: Option, 74 | ) -> Result { 75 | Err(packet::Error::IllegalOperation) 76 | } 77 | } 78 | 79 | #[derive(Debug, StructOpt)] 80 | struct Opt { 81 | archive_path: PathBuf, 82 | } 83 | 84 | fn main() -> Result<()> { 85 | // Parse args 86 | let opt = Opt::from_args(); 87 | 88 | fern::Dispatch::new() 89 | .level(log::LevelFilter::Info) 90 | .level_for("async_tftp", log::LevelFilter::Trace) 91 | .chain(std::io::stdout()) 92 | .apply() 93 | .expect("Failed to initialize logger"); 94 | 95 | block_on(async move { 96 | // We will serve files from a tar.gz through tftp 97 | let handler = TftpdTarGzHandler::new(&opt.archive_path); 98 | 99 | // Build server 100 | let tftpd = TftpServerBuilder::with_handler(handler) 101 | .bind("0.0.0.0:6969".parse().unwrap()) 102 | // Workaround to handle cases where client is behind VPN 103 | .block_size_limit(1024) 104 | .build() 105 | .await?; 106 | 107 | // Serve 108 | log::info!("Listening on: {}", tftpd.listen_addr()?); 109 | tftpd.serve().await?; 110 | 111 | Ok(()) 112 | }) 113 | } 114 | -------------------------------------------------------------------------------- /rfcs/rfc1350.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Network Working Group K. Sollins 8 | Request For Comments: 1350 MIT 9 | STD: 33 July 1992 10 | Obsoletes: RFC 783 11 | 12 | 13 | THE TFTP PROTOCOL (REVISION 2) 14 | 15 | Status of this Memo 16 | 17 | This RFC specifies an IAB standards track protocol for the Internet 18 | community, and requests discussion and suggestions for improvements. 19 | Please refer to the current edition of the "IAB Official Protocol 20 | Standards" for the standardization state and status of this protocol. 21 | Distribution of this memo is unlimited. 22 | 23 | Summary 24 | 25 | TFTP is a very simple protocol used to transfer files. It is from 26 | this that its name comes, Trivial File Transfer Protocol or TFTP. 27 | Each nonterminal packet is acknowledged separately. This document 28 | describes the protocol and its types of packets. The document also 29 | explains the reasons behind some of the design decisions. 30 | 31 | Acknowlegements 32 | 33 | The protocol was originally designed by Noel Chiappa, and was 34 | redesigned by him, Bob Baldwin and Dave Clark, with comments from 35 | Steve Szymanski. The current revision of the document includes 36 | modifications stemming from discussions with and suggestions from 37 | Larry Allen, Noel Chiappa, Dave Clark, Geoff Cooper, Mike Greenwald, 38 | Liza Martin, David Reed, Craig Milo Rogers (of USC-ISI), Kathy 39 | Yellick, and the author. The acknowledgement and retransmission 40 | scheme was inspired by TCP, and the error mechanism was suggested by 41 | PARC's EFTP abort message. 42 | 43 | The May, 1992 revision to fix the "Sorcerer's Apprentice" protocol 44 | bug [4] and other minor document problems was done by Noel Chiappa. 45 | 46 | This research was supported by the Advanced Research Projects Agency 47 | of the Department of Defense and was monitored by the Office of Naval 48 | Research under contract number N00014-75-C-0661. 49 | 50 | 1. Purpose 51 | 52 | TFTP is a simple protocol to transfer files, and therefore was named 53 | the Trivial File Transfer Protocol or TFTP. It has been implemented 54 | on top of the Internet User Datagram protocol (UDP or Datagram) [2] 55 | 56 | 57 | 58 | Sollins [Page 1] 59 | 60 | RFC 1350 TFTP Revision 2 July 1992 61 | 62 | 63 | so it may be used to move files between machines on different 64 | networks implementing UDP. (This should not exclude the possibility 65 | of implementing TFTP on top of other datagram protocols.) It is 66 | designed to be small and easy to implement. Therefore, it lacks most 67 | of the features of a regular FTP. The only thing it can do is read 68 | and write files (or mail) from/to a remote server. It cannot list 69 | directories, and currently has no provisions for user authentication. 70 | In common with other Internet protocols, it passes 8 bit bytes of 71 | data. 72 | 73 | Three modes of transfer are currently supported: netascii (This is 74 | ascii as defined in "USA Standard Code for Information Interchange" 75 | [1] with the modifications specified in "Telnet Protocol 76 | Specification" [3].) Note that it is 8 bit ascii. The term 77 | "netascii" will be used throughout this document to mean this 78 | particular version of ascii.); octet (This replaces the "binary" mode 79 | of previous versions of this document.) raw 8 bit bytes; mail, 80 | netascii characters sent to a user rather than a file. (The mail 81 | mode is obsolete and should not be implemented or used.) Additional 82 | modes can be defined by pairs of cooperating hosts. 83 | 84 | Reference [4] (section 4.2) should be consulted for further valuable 85 | directives and suggestions on TFTP. 86 | 87 | 2. Overview of the Protocol 88 | 89 | Any transfer begins with a request to read or write a file, which 90 | also serves to request a connection. If the server grants the 91 | request, the connection is opened and the file is sent in fixed 92 | length blocks of 512 bytes. Each data packet contains one block of 93 | data, and must be acknowledged by an acknowledgment packet before the 94 | next packet can be sent. A data packet of less than 512 bytes 95 | signals termination of a transfer. If a packet gets lost in the 96 | network, the intended recipient will timeout and may retransmit his 97 | last packet (which may be data or an acknowledgment), thus causing 98 | the sender of the lost packet to retransmit that lost packet. The 99 | sender has to keep just one packet on hand for retransmission, since 100 | the lock step acknowledgment guarantees that all older packets have 101 | been received. Notice that both machines involved in a transfer are 102 | considered senders and receivers. One sends data and receives 103 | acknowledgments, the other sends acknowledgments and receives data. 104 | 105 | Most errors cause termination of the connection. An error is 106 | signalled by sending an error packet. This packet is not 107 | acknowledged, and not retransmitted (i.e., a TFTP server or user may 108 | terminate after sending an error message), so the other end of the 109 | connection may not get it. Therefore timeouts are used to detect 110 | such a termination when the error packet has been lost. Errors are 111 | 112 | 113 | 114 | Sollins [Page 2] 115 | 116 | RFC 1350 TFTP Revision 2 July 1992 117 | 118 | 119 | caused by three types of events: not being able to satisfy the 120 | request (e.g., file not found, access violation, or no such user), 121 | receiving a packet which cannot be explained by a delay or 122 | duplication in the network (e.g., an incorrectly formed packet), and 123 | losing access to a necessary resource (e.g., disk full or access 124 | denied during a transfer). 125 | 126 | TFTP recognizes only one error condition that does not cause 127 | termination, the source port of a received packet being incorrect. 128 | In this case, an error packet is sent to the originating host. 129 | 130 | This protocol is very restrictive, in order to simplify 131 | implementation. For example, the fixed length blocks make allocation 132 | straight forward, and the lock step acknowledgement provides flow 133 | control and eliminates the need to reorder incoming data packets. 134 | 135 | 3. Relation to other Protocols 136 | 137 | As mentioned TFTP is designed to be implemented on top of the 138 | Datagram protocol (UDP). Since Datagram is implemented on the 139 | Internet protocol, packets will have an Internet header, a Datagram 140 | header, and a TFTP header. Additionally, the packets may have a 141 | header (LNI, ARPA header, etc.) to allow them through the local 142 | transport medium. As shown in Figure 3-1, the order of the contents 143 | of a packet will be: local medium header, if used, Internet header, 144 | Datagram header, TFTP header, followed by the remainder of the TFTP 145 | packet. (This may or may not be data depending on the type of packet 146 | as specified in the TFTP header.) TFTP does not specify any of the 147 | values in the Internet header. On the other hand, the source and 148 | destination port fields of the Datagram header (its format is given 149 | in the appendix) are used by TFTP and the length field reflects the 150 | size of the TFTP packet. The transfer identifiers (TID's) used by 151 | TFTP are passed to the Datagram layer to be used as ports; therefore 152 | they must be between 0 and 65,535. The initialization of TID's is 153 | discussed in the section on initial connection protocol. 154 | 155 | The TFTP header consists of a 2 byte opcode field which indicates 156 | the packet's type (e.g., DATA, ERROR, etc.) These opcodes and the 157 | formats of the various types of packets are discussed further in the 158 | section on TFTP packets. 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | Sollins [Page 3] 171 | 172 | RFC 1350 TFTP Revision 2 July 1992 173 | 174 | 175 | --------------------------------------------------- 176 | | Local Medium | Internet | Datagram | TFTP | 177 | --------------------------------------------------- 178 | 179 | Figure 3-1: Order of Headers 180 | 181 | 182 | 4. Initial Connection Protocol 183 | 184 | A transfer is established by sending a request (WRQ to write onto a 185 | foreign file system, or RRQ to read from it), and receiving a 186 | positive reply, an acknowledgment packet for write, or the first data 187 | packet for read. In general an acknowledgment packet will contain 188 | the block number of the data packet being acknowledged. Each data 189 | packet has associated with it a block number; block numbers are 190 | consecutive and begin with one. Since the positive response to a 191 | write request is an acknowledgment packet, in this special case the 192 | block number will be zero. (Normally, since an acknowledgment packet 193 | is acknowledging a data packet, the acknowledgment packet will 194 | contain the block number of the data packet being acknowledged.) If 195 | the reply is an error packet, then the request has been denied. 196 | 197 | In order to create a connection, each end of the connection chooses a 198 | TID for itself, to be used for the duration of that connection. The 199 | TID's chosen for a connection should be randomly chosen, so that the 200 | probability that the same number is chosen twice in immediate 201 | succession is very low. Every packet has associated with it the two 202 | TID's of the ends of the connection, the source TID and the 203 | destination TID. These TID's are handed to the supporting UDP (or 204 | other datagram protocol) as the source and destination ports. A 205 | requesting host chooses its source TID as described above, and sends 206 | its initial request to the known TID 69 decimal (105 octal) on the 207 | serving host. The response to the request, under normal operation, 208 | uses a TID chosen by the server as its source TID and the TID chosen 209 | for the previous message by the requestor as its destination TID. 210 | The two chosen TID's are then used for the remainder of the transfer. 211 | 212 | As an example, the following shows the steps used to establish a 213 | connection to write a file. Note that WRQ, ACK, and DATA are the 214 | names of the write request, acknowledgment, and data types of packets 215 | respectively. The appendix contains a similar example for reading a 216 | file. 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | Sollins [Page 4] 227 | 228 | RFC 1350 TFTP Revision 2 July 1992 229 | 230 | 231 | 1. Host A sends a "WRQ" to host B with source= A's TID, 232 | destination= 69. 233 | 234 | 2. Host B sends a "ACK" (with block number= 0) to host A with 235 | source= B's TID, destination= A's TID. 236 | 237 | At this point the connection has been established and the first data 238 | packet can be sent by Host A with a sequence number of 1. In the 239 | next step, and in all succeeding steps, the hosts should make sure 240 | that the source TID matches the value that was agreed on in steps 1 241 | and 2. If a source TID does not match, the packet should be 242 | discarded as erroneously sent from somewhere else. An error packet 243 | should be sent to the source of the incorrect packet, while not 244 | disturbing the transfer. This can be done only if the TFTP in fact 245 | receives a packet with an incorrect TID. If the supporting protocols 246 | do not allow it, this particular error condition will not arise. 247 | 248 | The following example demonstrates a correct operation of the 249 | protocol in which the above situation can occur. Host A sends a 250 | request to host B. Somewhere in the network, the request packet is 251 | duplicated, and as a result two acknowledgments are returned to host 252 | A, with different TID's chosen on host B in response to the two 253 | requests. When the first response arrives, host A continues the 254 | connection. When the second response to the request arrives, it 255 | should be rejected, but there is no reason to terminate the first 256 | connection. Therefore, if different TID's are chosen for the two 257 | connections on host B and host A checks the source TID's of the 258 | messages it receives, the first connection can be maintained while 259 | the second is rejected by returning an error packet. 260 | 261 | 5. TFTP Packets 262 | 263 | TFTP supports five types of packets, all of which have been mentioned 264 | above: 265 | 266 | opcode operation 267 | 1 Read request (RRQ) 268 | 2 Write request (WRQ) 269 | 3 Data (DATA) 270 | 4 Acknowledgment (ACK) 271 | 5 Error (ERROR) 272 | 273 | The TFTP header of a packet contains the opcode associated with 274 | that packet. 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | Sollins [Page 5] 283 | 284 | RFC 1350 TFTP Revision 2 July 1992 285 | 286 | 287 | 2 bytes string 1 byte string 1 byte 288 | ------------------------------------------------ 289 | | Opcode | Filename | 0 | Mode | 0 | 290 | ------------------------------------------------ 291 | 292 | Figure 5-1: RRQ/WRQ packet 293 | 294 | 295 | RRQ and WRQ packets (opcodes 1 and 2 respectively) have the format 296 | shown in Figure 5-1. The file name is a sequence of bytes in 297 | netascii terminated by a zero byte. The mode field contains the 298 | string "netascii", "octet", or "mail" (or any combination of upper 299 | and lower case, such as "NETASCII", NetAscii", etc.) in netascii 300 | indicating the three modes defined in the protocol. A host which 301 | receives netascii mode data must translate the data to its own 302 | format. Octet mode is used to transfer a file that is in the 8-bit 303 | format of the machine from which the file is being transferred. It 304 | is assumed that each type of machine has a single 8-bit format that 305 | is more common, and that that format is chosen. For example, on a 306 | DEC-20, a 36 bit machine, this is four 8-bit bytes to a word with 307 | four bits of breakage. If a host receives a octet file and then 308 | returns it, the returned file must be identical to the original. 309 | Mail mode uses the name of a mail recipient in place of a file and 310 | must begin with a WRQ. Otherwise it is identical to netascii mode. 311 | The mail recipient string should be of the form "username" or 312 | "username@hostname". If the second form is used, it allows the 313 | option of mail forwarding by a relay computer. 314 | 315 | The discussion above assumes that both the sender and recipient are 316 | operating in the same mode, but there is no reason that this has to 317 | be the case. For example, one might build a storage server. There 318 | is no reason that such a machine needs to translate netascii into its 319 | own form of text. Rather, the sender might send files in netascii, 320 | but the storage server might simply store them without translation in 321 | 8-bit format. Another such situation is a problem that currently 322 | exists on DEC-20 systems. Neither netascii nor octet accesses all 323 | the bits in a word. One might create a special mode for such a 324 | machine which read all the bits in a word, but in which the receiver 325 | stored the information in 8-bit format. When such a file is 326 | retrieved from the storage site, it must be restored to its original 327 | form to be useful, so the reverse mode must also be implemented. The 328 | user site will have to remember some information to achieve this. In 329 | both of these examples, the request packets would specify octet mode 330 | to the foreign host, but the local host would be in some other mode. 331 | No such machine or application specific modes have been specified in 332 | TFTP, but one would be compatible with this specification. 333 | 334 | It is also possible to define other modes for cooperating pairs of 335 | 336 | 337 | 338 | Sollins [Page 6] 339 | 340 | RFC 1350 TFTP Revision 2 July 1992 341 | 342 | 343 | hosts, although this must be done with care. There is no requirement 344 | that any other hosts implement these. There is no central authority 345 | that will define these modes or assign them names. 346 | 347 | 348 | 2 bytes 2 bytes n bytes 349 | ---------------------------------- 350 | | Opcode | Block # | Data | 351 | ---------------------------------- 352 | 353 | Figure 5-2: DATA packet 354 | 355 | 356 | Data is actually transferred in DATA packets depicted in Figure 5-2. 357 | DATA packets (opcode = 3) have a block number and data field. The 358 | block numbers on data packets begin with one and increase by one for 359 | each new block of data. This restriction allows the program to use a 360 | single number to discriminate between new packets and duplicates. 361 | The data field is from zero to 512 bytes long. If it is 512 bytes 362 | long, the block is not the last block of data; if it is from zero to 363 | 511 bytes long, it signals the end of the transfer. (See the section 364 | on Normal Termination for details.) 365 | 366 | All packets other than duplicate ACK's and those used for 367 | termination are acknowledged unless a timeout occurs [4]. Sending a 368 | DATA packet is an acknowledgment for the first ACK packet of the 369 | previous DATA packet. The WRQ and DATA packets are acknowledged by 370 | ACK or ERROR packets, while RRQ 371 | 372 | 373 | 2 bytes 2 bytes 374 | --------------------- 375 | | Opcode | Block # | 376 | --------------------- 377 | 378 | Figure 5-3: ACK packet 379 | 380 | 381 | and ACK packets are acknowledged by DATA or ERROR packets. Figure 382 | 5-3 depicts an ACK packet; the opcode is 4. The block number in 383 | an ACK echoes the block number of the DATA packet being 384 | acknowledged. A WRQ is acknowledged with an ACK packet having a 385 | block number of zero. 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | Sollins [Page 7] 395 | 396 | RFC 1350 TFTP Revision 2 July 1992 397 | 398 | 399 | 2 bytes 2 bytes string 1 byte 400 | ----------------------------------------- 401 | | Opcode | ErrorCode | ErrMsg | 0 | 402 | ----------------------------------------- 403 | 404 | Figure 5-4: ERROR packet 405 | 406 | 407 | An ERROR packet (opcode 5) takes the form depicted in Figure 5-4. An 408 | ERROR packet can be the acknowledgment of any other type of packet. 409 | The error code is an integer indicating the nature of the error. A 410 | table of values and meanings is given in the appendix. (Note that 411 | several error codes have been added to this version of this 412 | document.) The error message is intended for human consumption, and 413 | should be in netascii. Like all other strings, it is terminated with 414 | a zero byte. 415 | 416 | 6. Normal Termination 417 | 418 | The end of a transfer is marked by a DATA packet that contains 419 | between 0 and 511 bytes of data (i.e., Datagram length < 516). This 420 | packet is acknowledged by an ACK packet like all other DATA packets. 421 | The host acknowledging the final DATA packet may terminate its side 422 | of the connection on sending the final ACK. On the other hand, 423 | dallying is encouraged. This means that the host sending the final 424 | ACK will wait for a while before terminating in order to retransmit 425 | the final ACK if it has been lost. The acknowledger will know that 426 | the ACK has been lost if it receives the final DATA packet again. 427 | The host sending the last DATA must retransmit it until the packet is 428 | acknowledged or the sending host times out. If the response is an 429 | ACK, the transmission was completed successfully. If the sender of 430 | the data times out and is not prepared to retransmit any more, the 431 | transfer may still have been completed successfully, after which the 432 | acknowledger or network may have experienced a problem. It is also 433 | possible in this case that the transfer was unsuccessful. In any 434 | case, the connection has been closed. 435 | 436 | 7. Premature Termination 437 | 438 | If a request can not be granted, or some error occurs during the 439 | transfer, then an ERROR packet (opcode 5) is sent. This is only a 440 | courtesy since it will not be retransmitted or acknowledged, so it 441 | may never be received. Timeouts must also be used to detect errors. 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | Sollins [Page 8] 451 | 452 | RFC 1350 TFTP Revision 2 July 1992 453 | 454 | 455 | I. Appendix 456 | 457 | Order of Headers 458 | 459 | 2 bytes 460 | ---------------------------------------------------------- 461 | | Local Medium | Internet | Datagram | TFTP Opcode | 462 | ---------------------------------------------------------- 463 | 464 | TFTP Formats 465 | 466 | Type Op # Format without header 467 | 468 | 2 bytes string 1 byte string 1 byte 469 | ----------------------------------------------- 470 | RRQ/ | 01/02 | Filename | 0 | Mode | 0 | 471 | WRQ ----------------------------------------------- 472 | 2 bytes 2 bytes n bytes 473 | --------------------------------- 474 | DATA | 03 | Block # | Data | 475 | --------------------------------- 476 | 2 bytes 2 bytes 477 | ------------------- 478 | ACK | 04 | Block # | 479 | -------------------- 480 | 2 bytes 2 bytes string 1 byte 481 | ---------------------------------------- 482 | ERROR | 05 | ErrorCode | ErrMsg | 0 | 483 | ---------------------------------------- 484 | 485 | Initial Connection Protocol for reading a file 486 | 487 | 1. Host A sends a "RRQ" to host B with source= A's TID, 488 | destination= 69. 489 | 490 | 2. Host B sends a "DATA" (with block number= 1) to host A with 491 | source= B's TID, destination= A's TID. 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | Sollins [Page 9] 507 | 508 | RFC 1350 TFTP Revision 2 July 1992 509 | 510 | 511 | Error Codes 512 | 513 | Value Meaning 514 | 515 | 0 Not defined, see error message (if any). 516 | 1 File not found. 517 | 2 Access violation. 518 | 3 Disk full or allocation exceeded. 519 | 4 Illegal TFTP operation. 520 | 5 Unknown transfer ID. 521 | 6 File already exists. 522 | 7 No such user. 523 | 524 | Internet User Datagram Header [2] 525 | 526 | (This has been included only for convenience. TFTP need not be 527 | implemented on top of the Internet User Datagram Protocol.) 528 | 529 | Format 530 | 531 | 0 1 2 3 532 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 533 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 534 | | Source Port | Destination Port | 535 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 536 | | Length | Checksum | 537 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 538 | 539 | 540 | Values of Fields 541 | 542 | 543 | Source Port Picked by originator of packet. 544 | 545 | Dest. Port Picked by destination machine (69 for RRQ or WRQ). 546 | 547 | Length Number of bytes in UDP packet, including UDP header. 548 | 549 | Checksum Reference 2 describes rules for computing checksum. 550 | (The implementor of this should be sure that the 551 | correct algorithm is used here.) 552 | Field contains zero if unused. 553 | 554 | Note: TFTP passes transfer identifiers (TID's) to the Internet User 555 | Datagram protocol to be used as the source and destination ports. 556 | 557 | 558 | 559 | 560 | 561 | 562 | Sollins [Page 10] 563 | 564 | RFC 1350 TFTP Revision 2 July 1992 565 | 566 | 567 | References 568 | 569 | [1] USA Standard Code for Information Interchange, USASI X3.4-1968. 570 | 571 | [2] Postel, J., "User Datagram Protocol," RFC 768, USC/Information 572 | Sciences Institute, 28 August 1980. 573 | 574 | [3] Postel, J., "Telnet Protocol Specification," RFC 764, 575 | USC/Information Sciences Institute, June, 1980. 576 | 577 | [4] Braden, R., Editor, "Requirements for Internet Hosts -- 578 | Application and Support", RFC 1123, USC/Information Sciences 579 | Institute, October 1989. 580 | 581 | Security Considerations 582 | 583 | Since TFTP includes no login or access control mechanisms, care must 584 | be taken in the rights granted to a TFTP server process so as not to 585 | violate the security of the server hosts file system. TFTP is often 586 | installed with controls such that only files that have public read 587 | access are available via TFTP and writing files via TFTP is 588 | disallowed. 589 | 590 | Author's Address 591 | 592 | Karen R. Sollins 593 | Massachusetts Institute of Technology 594 | Laboratory for Computer Science 595 | 545 Technology Square 596 | Cambridge, MA 02139-1986 597 | 598 | Phone: (617) 253-6006 599 | 600 | EMail: SOLLINS@LCS.MIT.EDU 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | Sollins [Page 11] 619 | -------------------------------------------------------------------------------- /rfcs/rfc2347.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Network Working Group G. Malkin 8 | Request for Commments: 2347 Bay Networks 9 | Updates: 1350 A. Harkin 10 | Obsoletes: 1782 Hewlett Packard Co. 11 | Category: Standards Track May 1998 12 | 13 | 14 | TFTP Option Extension 15 | 16 | Status of this Memo 17 | 18 | This document specifies an Internet standards track protocol for the 19 | Internet community, and requests discussion and suggestions for 20 | improvements. Please refer to the current edition of the "Internet 21 | Official Protocol Standards" (STD 1) for the standardization state 22 | and status of this protocol. Distribution of this memo is unlimited. 23 | 24 | Copyright Notice 25 | 26 | Copyright (C) The Internet Society (1998). All Rights Reserved. 27 | 28 | Abstract 29 | 30 | The Trivial File Transfer Protocol [1] is a simple, lock-step, file 31 | transfer protocol which allows a client to get or put a file onto a 32 | remote host. This document describes a simple extension to TFTP to 33 | allow option negotiation prior to the file transfer. 34 | 35 | Introduction 36 | 37 | The option negotiation mechanism proposed in this document is a 38 | backward-compatible extension to the TFTP protocol. It allows file 39 | transfer options to be negotiated prior to the transfer using a 40 | mechanism which is consistent with TFTP's Request Packet format. The 41 | mechanism is kept simple by enforcing a request-respond-acknowledge 42 | sequence, similar to the lock-step approach taken by TFTP itself. 43 | 44 | While the option negotiation mechanism is general purpose, in that 45 | many types of options may be negotiated, it was created to support 46 | the Blocksize option defined in [2]. Additional options are defined 47 | in [3]. 48 | 49 | Packet Formats 50 | 51 | TFTP options are appended to the Read Request and Write Request 52 | packets. A new type of TFTP packet, the Option Acknowledgment 53 | (OACK), is used to acknowledge a client's option negotiation request. 54 | A new error code, 8, is hereby defined to indicate that a transfer 55 | 56 | 57 | 58 | Malkin & Harkin Standards Track [Page 1] 59 | 60 | RFC 2347 TFTP Option Extension May 1998 61 | 62 | 63 | should be terminated due to option negotiation. 64 | 65 | Options are appended to a TFTP Read Request or Write Request packet 66 | as follows: 67 | 68 | +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+--> 69 | | opc |filename| 0 | mode | 0 | opt1 | 0 | value1 | 0 | < 70 | +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+--> 71 | 72 | >-------+---+---~~---+---+ 73 | < optN | 0 | valueN | 0 | 74 | >-------+---+---~~---+---+ 75 | 76 | opc 77 | The opcode field contains either a 1, for Read Requests, or 2, 78 | for Write Requests, as defined in [1]. 79 | 80 | filename 81 | The name of the file to be read or written, as defined in [1]. 82 | This is a NULL-terminated field. 83 | 84 | mode 85 | The mode of the file transfer: "netascii", "octet", or "mail", 86 | as defined in [1]. This is a NULL-terminated field. 87 | 88 | opt1 89 | The first option, in case-insensitive ASCII (e.g., blksize). 90 | This is a NULL-terminated field. 91 | 92 | value1 93 | The value associated with the first option, in case- 94 | insensitive ASCII. This is a NULL-terminated field. 95 | 96 | optN, valueN 97 | The final option/value pair. Each NULL-terminated field is 98 | specified in case-insensitive ASCII. 99 | 100 | The options and values are all NULL-terminated, in keeping with the 101 | original request format. If multiple options are to be negotiated, 102 | they are appended to each other. The order in which options are 103 | specified is not significant. The maximum size of a request packet 104 | is 512 octets. 105 | 106 | The OACK packet has the following format: 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | Malkin & Harkin Standards Track [Page 2] 115 | 116 | RFC 2347 TFTP Option Extension May 1998 117 | 118 | 119 | +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ 120 | | opc | opt1 | 0 | value1 | 0 | optN | 0 | valueN | 0 | 121 | +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ 122 | 123 | opc 124 | The opcode field contains a 6, for Option Acknowledgment. 125 | 126 | opt1 127 | The first option acknowledgment, copied from the original 128 | request. 129 | 130 | value1 131 | The acknowledged value associated with the first option. If 132 | and how this value may differ from the original request is 133 | detailed in the specification for the option. 134 | 135 | optN, valueN 136 | The final option/value acknowledgment pair. 137 | 138 | Negotiation Protocol 139 | 140 | The client appends options at the end of the Read Request or Write 141 | request packet, as shown above. Any number of options may be 142 | specified; however, an option may only be specified once. The order 143 | of the options is not significant. 144 | 145 | If the server supports option negotiation, and it recognizes one or 146 | more of the options specified in the request packet, the server may 147 | respond with an Options Acknowledgment (OACK). Each option the 148 | server recognizes, and accepts the value for, is included in the 149 | OACK. Some options may allow alternate values to be proposed, but 150 | this is an option specific feature. The server must not include in 151 | the OACK any option which had not been specifically requested by the 152 | client; that is, only the client may initiate option negotiation. 153 | Options which the server does not support should be omitted from the 154 | OACK; they should not cause an ERROR packet to be generated. If the 155 | value of a supported option is invalid, the specification for that 156 | option will indicate whether the server should simply omit the option 157 | from the OACK, respond with an alternate value, or send an ERROR 158 | packet, with error code 8, to terminate the transfer. 159 | 160 | An option not acknowledged by the server must be ignored by the 161 | client and server as if it were never requested. If multiple options 162 | were requested, the client must use those options which were 163 | acknowledged by the server and must not use those options which were 164 | not acknowledged by the server. 165 | 166 | 167 | 168 | 169 | 170 | Malkin & Harkin Standards Track [Page 3] 171 | 172 | RFC 2347 TFTP Option Extension May 1998 173 | 174 | 175 | When the client appends options to the end of a Read Request packet, 176 | three possible responses may be returned by the server: 177 | 178 | OACK - acknowledge of Read Request and the options; 179 | 180 | DATA - acknowledge of Read Request, but not the options; 181 | 182 | ERROR - the request has been denied. 183 | 184 | When the client appends options to the end of a Write Request packet, 185 | three possible responses may be returned by the server: 186 | 187 | OACK - acknowledge of Write Request and the options; 188 | 189 | ACK - acknowledge of Write Request, but not the options; 190 | 191 | ERROR - the request has been denied. 192 | 193 | If a server implementation does not support option negotiation, it 194 | will likely ignore any options appended to the client's request. In 195 | this case, the server will return a DATA packet for a Read Request 196 | and an ACK packet for a Write Request establishing normal TFTP data 197 | transfer. In the event that a server returns an error for a request 198 | which carries an option, the client may attempt to repeat the request 199 | without appending any options. This implementation option would 200 | handle servers which consider extraneous data in the request packet 201 | to be erroneous. 202 | 203 | Depending on the original transfer request there are two ways for a 204 | client to confirm acceptance of a server's OACK. If the transfer was 205 | initiated with a Read Request, then an ACK (with the data block 206 | number set to 0) is sent by the client to confirm the values in the 207 | server's OACK packet. If the transfer was initiated with a Write 208 | Request, then the client begins the transfer with the first DATA 209 | packet, using the negotiated values. If the client rejects the OACK, 210 | then it sends an ERROR packet, with error code 8, to the server and 211 | the transfer is terminated. 212 | 213 | Once a client acknowledges an OACK, with an appropriate non-error 214 | response, that client has agreed to use only the options and values 215 | returned by the server. Remember that the server cannot request an 216 | option; it can only respond to them. If the client receives an OACK 217 | containing an unrequested option, it should respond with an ERROR 218 | packet, with error code 8, and terminate the transfer. 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | Malkin & Harkin Standards Track [Page 4] 227 | 228 | RFC 2347 TFTP Option Extension May 1998 229 | 230 | 231 | Examples 232 | 233 | Read Request 234 | 235 | client server 236 | ------------------------------------------------------- 237 | |1|foofile|0|octet|0|blksize|0|1432|0| --> RRQ 238 | <-- |6|blksize|0|1432|0| OACK 239 | |4|0| --> ACK 240 | <-- |3|1| 1432 octets of data | DATA 241 | |4|1| --> ACK 242 | <-- |3|2| 1432 octets of data | DATA 243 | |4|2| --> ACK 244 | <-- |3|3|<1432 octets of data | DATA 245 | |4|3| --> ACK 246 | 247 | Write Request 248 | 249 | client server 250 | ------------------------------------------------------- 251 | |2|barfile|0|octet|0|blksize|0|2048|0| --> RRQ 252 | <-- |6|blksize|0|2048|0| OACK 253 | |3|1| 2048 octets of data | --> DATA 254 | <-- |4|1| ACK 255 | |3|2| 2048 octets of data | --> DATA 256 | <-- |4|2| ACK 257 | |3|3|<2048 octets of data | --> DATA 258 | <-- |4|3| ACK 259 | 260 | Security Considerations 261 | 262 | The basic TFTP protocol has no security mechanism. This is why it 263 | has no rename, delete, or file overwrite capabilities. This document 264 | does not add any security to TFTP; however, the specified extensions 265 | do not add any additional security risks. 266 | 267 | References 268 | 269 | [1] Sollins, K., "The TFTP Protocol (Revision 2)", STD 33, RFC 1350, 270 | October 1992. 271 | 272 | [2] Malkin, G., and A. Harkin, "TFTP Blocksize Option", RFC 2348, 273 | May 1998. 274 | 275 | [3] Malkin, G., and A. Harkin, "TFTP Timeout Interval and Transfer 276 | Size Options", RFC 2349, May 1998. 277 | 278 | 279 | 280 | 281 | 282 | Malkin & Harkin Standards Track [Page 5] 283 | 284 | RFC 2347 TFTP Option Extension May 1998 285 | 286 | 287 | Authors' Addresses 288 | 289 | Gary Scott Malkin 290 | Bay Networks 291 | 8 Federal Street 292 | Billerica, MA 01821 293 | 294 | Phone: (978) 916-4237 295 | EMail: gmalkin@baynetworks.com 296 | 297 | 298 | Art Harkin 299 | Internet Services Project 300 | Information Networks Division 301 | 19420 Homestead Road MS 43LN 302 | Cupertino, CA 95014 303 | 304 | Phone: (408) 447-3755 305 | EMail: ash@cup.hp.com 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | Malkin & Harkin Standards Track [Page 6] 339 | 340 | RFC 2347 TFTP Option Extension May 1998 341 | 342 | 343 | Full Copyright Statement 344 | 345 | Copyright (C) The Internet Society (1998). All Rights Reserved. 346 | 347 | This document and translations of it may be copied and furnished to 348 | others, and derivative works that comment on or otherwise explain it 349 | or assist in its implementation may be prepared, copied, published 350 | and distributed, in whole or in part, without restriction of any 351 | kind, provided that the above copyright notice and this paragraph are 352 | included on all such copies and derivative works. However, this 353 | document itself may not be modified in any way, such as by removing 354 | the copyright notice or references to the Internet Society or other 355 | Internet organizations, except as needed for the purpose of 356 | developing Internet standards in which case the procedures for 357 | copyrights defined in the Internet Standards process must be 358 | followed, or as required to translate it into languages other than 359 | English. 360 | 361 | The limited permissions granted above are perpetual and will not be 362 | revoked by the Internet Society or its successors or assigns. 363 | 364 | This document and the information contained herein is provided on an 365 | "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING 366 | TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING 367 | BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION 368 | HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF 369 | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | Malkin & Harkin Standards Track [Page 7] 395 | 396 | -------------------------------------------------------------------------------- /rfcs/rfc2348.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Network Working Group G. Malkin 8 | Request for Commments: 2348 Bay Networks 9 | Updates: 1350 A. Harkin 10 | Obsoletes: 1783 Hewlett Packard Co. 11 | Category: Standards Track May 1998 12 | 13 | 14 | TFTP Blocksize Option 15 | 16 | Status of this Memo 17 | 18 | This document specifies an Internet standards track protocol for the 19 | Internet community, and requests discussion and suggestions for 20 | improvements. Please refer to the current edition of the "Internet 21 | Official Protocol Standards" (STD 1) for the standardization state 22 | and status of this protocol. Distribution of this memo is unlimited. 23 | 24 | Copyright Notice 25 | 26 | Copyright (C) The Internet Society (1998). All Rights Reserved. 27 | 28 | Abstract 29 | 30 | The Trivial File Transfer Protocol [1] is a simple, lock-step, file 31 | transfer protocol which allows a client to get or put a file onto a 32 | remote host. One of its primary uses is the booting of diskless 33 | nodes on a Local Area Network. TFTP is used because it is very 34 | simple to implement in a small node's limited ROM space. However, 35 | the choice of a 512-octet blocksize is not the most efficient for use 36 | on a LAN whose MTU may 1500 octets or greater. 37 | 38 | This document describes a TFTP option which allows the client and 39 | server to negotiate a blocksize more applicable to the network 40 | medium. The TFTP Option Extension mechanism is described in [2]. 41 | 42 | Blocksize Option Specification 43 | 44 | The TFTP Read Request or Write Request packet is modified to include 45 | the blocksize option as follows. Note that all fields except "opc" 46 | are NULL-terminated. 47 | 48 | +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ 49 | | opc |filename| 0 | mode | 0 | blksize| 0 | #octets| 0 | 50 | +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ 51 | 52 | opc 53 | The opcode field contains either a 1, for Read Requests, or 2, 54 | for Write Requests, as defined in [1]. 55 | 56 | 57 | 58 | Malkin & Harkin Standards Track [Page 1] 59 | 60 | RFC 2348 TFTP Blocksize Option May 1998 61 | 62 | 63 | filename 64 | The name of the file to be read or written, as defined in [1]. 65 | 66 | mode 67 | The mode of the file transfer: "netascii", "octet", or "mail", 68 | as defined in [1]. 69 | 70 | blksize 71 | The Blocksize option, "blksize" (case in-sensitive). 72 | 73 | #octets 74 | The number of octets in a block, specified in ASCII. Valid 75 | values range between "8" and "65464" octets, inclusive. The 76 | blocksize refers to the number of data octets; it does not 77 | include the four octets of TFTP header. 78 | 79 | For example: 80 | 81 | +-------+--------+---+--------+---+--------+---+--------+---+ 82 | | 1 | foobar | 0 | octet | 0 | blksize| 0 | 1428 | 0 | 83 | +-------+--------+---+--------+---+--------+---+--------+---+ 84 | 85 | is a Read Request, for the file named "foobar", in octet (binary) 86 | transfer mode, with a block size of 1428 octets (Ethernet MTU, less 87 | the TFTP, UDP and IP header lengths). 88 | 89 | If the server is willing to accept the blocksize option, it sends an 90 | Option Acknowledgment (OACK) to the client. The specified value must 91 | be less than or equal to the value specified by the client. The 92 | client must then either use the size specified in the OACK, or send 93 | an ERROR packet, with error code 8, to terminate the transfer. 94 | 95 | The rules for determining the final packet are unchanged from [1]. 96 | The reception of a data packet with a data length less than the 97 | negotiated blocksize is the final packet. If the blocksize is 98 | greater than the amount of data to be transfered, the first packet is 99 | the final packet. If the amount of data to be transfered is an 100 | integral multiple of the blocksize, an extra data packet containing 101 | no data is sent to end the transfer. 102 | 103 | Proof of Concept 104 | 105 | Performance tests were run on the prototype implementation using a 106 | variety of block sizes. The tests were run on a lightly loaded 107 | Ethernet, between two HP-UX 9000, in "octet" mode, on 2.25MB files. 108 | The average (5x) transfer times for paths with (g-time) and without 109 | (n-time) a intermediate gateway are graphed as follows: 110 | 111 | 112 | 113 | 114 | Malkin & Harkin Standards Track [Page 2] 115 | 116 | RFC 2348 TFTP Blocksize Option May 1998 117 | 118 | 119 | | 120 | 37 + g 121 | | 122 | 35 + 123 | | 124 | 33 + 125 | | 126 | 31 + 127 | | 128 | 29 + 129 | | 130 | 27 + 131 | | g blocksize n-time g-time 132 | 25 + --------- ------ ------ 133 | s | n 512 23.85 37.05 134 | e 23 + g 1024 16.15 25.65 135 | c | 1428 13.70 23.10 136 | o 21 + 2048 10.90 16.90 137 | n | 4096 6.85 9.65 138 | d 19 + 8192 4.90 6.15 139 | s | 140 | 17 + g 141 | | n 142 | 15 + 143 | | n 144 | 13 + 145 | | 146 | 11 + n 147 | | g 148 | 9 + 149 | | 150 | 7 + n 151 | | g 152 | 5 + n 153 | " 154 | 0 +------+------+--+---+------+------+--- 155 | 512 1K | 2K 4K 8K 156 | 1428 157 | blocksize (octets) 158 | 159 | The comparisons between transfer times (without a gateway) between 160 | the standard 512-octet blocksize and the negotiated blocksizes are: 161 | 162 | 1024 2x -32% 163 | 1428 2.8x -42% 164 | 2048 4x -54% 165 | 4096 8x -71% 166 | 8192 16x -80% 167 | 168 | 169 | 170 | Malkin & Harkin Standards Track [Page 3] 171 | 172 | RFC 2348 TFTP Blocksize Option May 1998 173 | 174 | 175 | As was anticipated, the transfer time decreases with an increase in 176 | blocksize. The reason for the reduction in time is the reduction in 177 | the number of packets sent. For example, by increasing the blocksize 178 | from 512 octets to 1024 octets, not only are the number of data 179 | packets halved, but the number of acknowledgement packets is also 180 | halved (along with the number of times the data transmitter must wait 181 | for an ACK). A secondary effect is the efficiency gained by reducing 182 | the per-packet framing and processing overhead. 183 | 184 | Of course, if the blocksize exceeds the path MTU, IP fragmentation 185 | and reassembly will begin to add more overhead. This will be more 186 | noticable the greater the number of gateways in the path. 187 | 188 | Security Considerations 189 | 190 | The basic TFTP protocol has no security mechanism. This is why it 191 | has no rename, delete, or file overwrite capabilities. This document 192 | does not add any security to TFTP; however, the specified extensions 193 | do not add any additional security risks. 194 | 195 | References 196 | 197 | [1] Sollins, K., "The TFTP Protocol (Revision 2)", STD 33, RFC 1350, 198 | October 1992. 199 | 200 | [2] Malkin, G., and A. Harkin, "TFTP Option Extension", RFC 2347, 201 | May 1998. 202 | 203 | Authors' Addresses 204 | 205 | Gary Scott Malkin 206 | Bay Networks 207 | 8 Federal Street 208 | Billerica, MA 10821 209 | 210 | Phone: (978) 916-4237 211 | EMail: gmalkin@baynetworks.com 212 | 213 | 214 | Art Harkin 215 | Networked Computing Division 216 | Hewlett-Packard Company 217 | 19420 Homestead Road MS 43LN 218 | Cupertino, CA 95014 219 | 220 | Phone: (408) 447-3755 221 | EMail: ash@cup.hp.com 222 | 223 | 224 | 225 | 226 | Malkin & Harkin Standards Track [Page 4] 227 | 228 | RFC 2348 TFTP Blocksize Option May 1998 229 | 230 | 231 | Full Copyright Statement 232 | 233 | Copyright (C) The Internet Society (1998). All Rights Reserved. 234 | 235 | This document and translations of it may be copied and furnished to 236 | others, and derivative works that comment on or otherwise explain it 237 | or assist in its implementation may be prepared, copied, published 238 | and distributed, in whole or in part, without restriction of any 239 | kind, provided that the above copyright notice and this paragraph are 240 | included on all such copies and derivative works. However, this 241 | document itself may not be modified in any way, such as by removing 242 | the copyright notice or references to the Internet Society or other 243 | Internet organizations, except as needed for the purpose of 244 | developing Internet standards in which case the procedures for 245 | copyrights defined in the Internet Standards process must be 246 | followed, or as required to translate it into languages other than 247 | English. 248 | 249 | The limited permissions granted above are perpetual and will not be 250 | revoked by the Internet Society or its successors or assigns. 251 | 252 | This document and the information contained herein is provided on an 253 | "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING 254 | TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING 255 | BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION 256 | HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF 257 | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | Malkin & Harkin Standards Track [Page 5] 283 | 284 | -------------------------------------------------------------------------------- /rfcs/rfc2349.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Network Working Group G. Malkin 8 | Request for Commments: 2349 Bay Networks 9 | Updates: 1350 A. Harkin 10 | Obsoletes: 1784 Hewlett Packard Co. 11 | Category: Standards Track May 1998 12 | 13 | 14 | TFTP Timeout Interval and Transfer Size Options 15 | 16 | Status of this Memo 17 | 18 | This document specifies an Internet standards track protocol for the 19 | Internet community, and requests discussion and suggestions for 20 | improvements. Please refer to the current edition of the "Internet 21 | Official Protocol Standards" (STD 1) for the standardization state 22 | and status of this protocol. Distribution of this memo is unlimited. 23 | 24 | Copyright Notice 25 | 26 | Copyright (C) The Internet Society (1998). All Rights Reserved. 27 | 28 | Abstract 29 | 30 | The Trivial File Transfer Protocol [1] is a simple, lock-step, file 31 | transfer protocol which allows a client to get or put a file onto a 32 | remote host. 33 | 34 | This document describes two TFTP options. The first allows the client 35 | and server to negotiate the Timeout Interval. The second allows the 36 | side receiving the file to determine the ultimate size of the 37 | transfer before it begins. The TFTP Option Extension mechanism is 38 | described in [2]. 39 | 40 | Timeout Interval Option Specification 41 | 42 | The TFTP Read Request or Write Request packet is modified to include 43 | the timeout option as follows: 44 | 45 | +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ 46 | | opc |filename| 0 | mode | 0 | timeout| 0 | #secs | 0 | 47 | +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ 48 | 49 | opc 50 | The opcode field contains either a 1, for Read Requests, or 2, 51 | for Write Requests, as defined in [1]. 52 | 53 | 54 | 55 | 56 | 57 | 58 | Malkin & Harkin Standards Track [Page 1] 59 | 60 | RFC 2349 TFTP Timeout Interval and Transfer Size Options May 1998 61 | 62 | 63 | filename 64 | The name of the file to be read or written, as defined in [1]. 65 | This is a NULL-terminated field. 66 | 67 | mode 68 | The mode of the file transfer: "netascii", "octet", or "mail", 69 | as defined in [1]. This is a NULL-terminated field. 70 | 71 | timeout 72 | The Timeout Interval option, "timeout" (case in-sensitive). 73 | This is a NULL-terminated field. 74 | 75 | #secs 76 | The number of seconds to wait before retransmitting, specified 77 | in ASCII. Valid values range between "1" and "255" seconds, 78 | inclusive. This is a NULL-terminated field. 79 | 80 | For example: 81 | 82 | +-------+--------+---+--------+---+--------+---+-------+---+ 83 | | 1 | foobar | 0 | octet | 0 | timeout| 0 | 1 | 0 | 84 | +-------+--------+---+--------+---+--------+---+-------+---+ 85 | 86 | is a Read Request, for the file named "foobar", in octet (binary) 87 | transfer mode, with a timeout interval of 1 second. 88 | 89 | If the server is willing to accept the timeout option, it sends an 90 | Option Acknowledgment (OACK) to the client. The specified timeout 91 | value must match the value specified by the client. 92 | 93 | Transfer Size Option Specification 94 | 95 | The TFTP Read Request or Write Request packet is modified to include 96 | the tsize option as follows: 97 | 98 | +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ 99 | | opc |filename| 0 | mode | 0 | tsize | 0 | size | 0 | 100 | +-------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ 101 | 102 | opc 103 | The opcode field contains either a 1, for Read Requests, or 2, 104 | for Write Requests, as defined in [1]. 105 | 106 | filename 107 | The name of the file to be read or written, as defined in [1]. 108 | This is a NULL-terminated field. 109 | 110 | 111 | 112 | 113 | 114 | Malkin & Harkin Standards Track [Page 2] 115 | 116 | RFC 2349 TFTP Timeout Interval and Transfer Size Options May 1998 117 | 118 | 119 | mode 120 | The mode of the file transfer: "netascii", "octet", or "mail", 121 | as defined in [1]. This is a NULL-terminated field. 122 | 123 | tsize 124 | The Transfer Size option, "tsize" (case in-sensitive). This is 125 | a NULL-terminated field. 126 | 127 | size 128 | The size of the file to be transfered. This is a NULL- 129 | terminated field. 130 | 131 | For example: 132 | 133 | +-------+--------+---+--------+---+--------+---+--------+---+ 134 | | 2 | foobar | 0 | octet | 0 | tsize | 0 | 673312 | 0 | 135 | +-------+--------+---+--------+---+--------+---+--------+---+ 136 | 137 | is a Write Request, with the 673312-octet file named "foobar", in 138 | octet (binary) transfer mode. 139 | 140 | In Read Request packets, a size of "0" is specified in the request 141 | and the size of the file, in octets, is returned in the OACK. If the 142 | file is too large for the client to handle, it may abort the transfer 143 | with an Error packet (error code 3). In Write Request packets, the 144 | size of the file, in octets, is specified in the request and echoed 145 | back in the OACK. If the file is too large for the server to handle, 146 | it may abort the transfer with an Error packet (error code 3). 147 | 148 | Security Considerations 149 | 150 | The basic TFTP protocol has no security mechanism. This is why it 151 | has no rename, delete, or file overwrite capabilities. This document 152 | does not add any security to TFTP; however, the specified extensions 153 | do not add any additional security risks. 154 | 155 | References 156 | 157 | [1] Sollins, K., "The TFTP Protocol (Revision 2)", STD 33, RFC 1350, 158 | October 92. 159 | 160 | [2] Malkin, G., and A. Harkin, "TFTP Option Extension", RFC 2347, 161 | May 1998. 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | Malkin & Harkin Standards Track [Page 3] 171 | 172 | RFC 2349 TFTP Timeout Interval and Transfer Size Options May 1998 173 | 174 | 175 | Authors' Addresses 176 | 177 | Gary Scott Malkin 178 | Bay Networks 179 | 8 Federal Street 180 | Billerica, MA 01821 181 | 182 | Phone: (978) 916-4237 183 | EMail: gmalkin@baynetworks.com 184 | 185 | 186 | Art Harkin 187 | Internet Services Project 188 | Information Networks Division 189 | 19420 Homestead Road MS 43LN 190 | Cupertino, CA 95014 191 | 192 | Phone: (408) 447-3755 193 | EMail: ash@cup.hp.com 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | Malkin & Harkin Standards Track [Page 4] 227 | 228 | RFC 2349 TFTP Timeout Interval and Transfer Size Options May 1998 229 | 230 | 231 | Full Copyright Statement 232 | 233 | Copyright (C) The Internet Society (1998). All Rights Reserved. 234 | 235 | This document and translations of it may be copied and furnished to 236 | others, and derivative works that comment on or otherwise explain it 237 | or assist in its implementation may be prepared, copied, published 238 | and distributed, in whole or in part, without restriction of any 239 | kind, provided that the above copyright notice and this paragraph are 240 | included on all such copies and derivative works. However, this 241 | document itself may not be modified in any way, such as by removing 242 | the copyright notice or references to the Internet Society or other 243 | Internet organizations, except as needed for the purpose of 244 | developing Internet standards in which case the procedures for 245 | copyrights defined in the Internet Standards process must be 246 | followed, or as required to translate it into languages other than 247 | English. 248 | 249 | The limited permissions granted above are perpetual and will not be 250 | revoked by the Internet Society or its successors or assigns. 251 | 252 | This document and the information contained herein is provided on an 253 | "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING 254 | TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING 255 | BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION 256 | HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF 257 | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | Malkin & Harkin Standards Track [Page 5] 283 | 284 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | use_small_heuristics = "off" 3 | edition = "2018" 4 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | /// Type alias to [`Result`](std::result::Result). 4 | pub type Result = std::result::Result; 5 | 6 | /// Error type of this crate. 7 | #[derive(Debug, Error)] 8 | pub enum Error { 9 | #[error("Invalid packet")] 10 | InvalidPacket, 11 | 12 | #[error("TFTP protocol error: {0:?}")] 13 | Packet(crate::packet::Error), 14 | 15 | #[error("IO error: {0}")] 16 | Io(#[from] std::io::Error), 17 | 18 | #[error("Failed to bind socket: {0}")] 19 | Bind(#[source] std::io::Error), 20 | 21 | #[error("Path '{}' is not a directory", .0.display())] 22 | NotDir(std::path::PathBuf), 23 | 24 | #[error("Max send retries reached (peer: {0}, block id: {1})")] 25 | MaxSendRetriesReached(std::net::SocketAddr, u16), 26 | } 27 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Executor agnostic async TFTP implementation, written with [smol] 2 | //! building blocks. Currently it implements only server side. 3 | //! 4 | //! The following RFCs are implemented: 5 | //! 6 | //! * [RFC 1350] - The TFTP Protocol (Revision 2). 7 | //! * [RFC 2347] - TFTP Option Extension. 8 | //! * [RFC 2348] - TFTP Blocksize Option. 9 | //! * [RFC 2349] - TFTP Timeout Interval and Transfer Size Options. 10 | //! 11 | //! Features: 12 | //! 13 | //! * Async implementation. 14 | //! * Works with any runtime/executor. 15 | //! * Serve read (RRQ) and write (WRQ) requests. 16 | //! * Unlimited transfer file size (block number roll-over). 17 | //! * You can set non-standard reply [`timeout`]. This is useful for faster 18 | //! file transfer in unstable environments. 19 | //! * You can set [block size limit]. This is useful if you are accessing 20 | //! client through a VPN. 21 | //! * You can implement your own [`Handler`] for more advance cases than 22 | //! just serving a directory. Check [`tftpd-targz.rs`] for an example. 23 | //! 24 | //! # Example 25 | //! 26 | //! ```ignore 27 | //! use async_tftp::server::TftpServerBuilder; 28 | //! use async_tftp::Result; 29 | //! 30 | //! #[tokio::main] // or any other runtime/executor 31 | //! async fn main() -> Result<()> { 32 | //! let tftpd = TftpServerBuilder::with_dir_ro(".")?.build().await?; 33 | //! tftpd.serve().await?; 34 | //! Ok(()) 35 | //! } 36 | //! ``` 37 | //! 38 | //! Add in `Cargo.toml`: 39 | //! 40 | //! ```toml 41 | //! [dependencies] 42 | //! async-tftp = "0.3" 43 | //! # or any other runtime/executor 44 | //! tokio = { version = "1", features = ["rt-multi-thread", "macros"] } 45 | //! ``` 46 | //! 47 | //! [smol]: https://docs.rs/smol 48 | //! 49 | //! [`timeout`]: server::TftpServerBuilder::timeout 50 | //! [block size limit]: server::TftpServerBuilder::block_size_limit 51 | //! [`Handler`]: server::Handler 52 | //! [`tftpd-targz.rs`]: https://github.com/oblique/async-tftp-rs/blob/master/examples/tftpd-targz.rs 53 | //! 54 | //! [RFC 1350]: https://tools.ietf.org/html/rfc1350 55 | //! [RFC 2347]: https://tools.ietf.org/html/rfc2347 56 | //! [RFC 2348]: https://tools.ietf.org/html/rfc2348 57 | //! [RFC 2349]: https://tools.ietf.org/html/rfc2349 58 | 59 | pub mod server; 60 | 61 | /// Packet definitions that are needed in public API. 62 | pub mod packet; 63 | 64 | mod error; 65 | mod parse; 66 | mod tests; 67 | mod utils; 68 | 69 | pub use crate::error::*; 70 | -------------------------------------------------------------------------------- /src/packet.rs: -------------------------------------------------------------------------------- 1 | //! Packet definitions. 2 | use bytes::{BufMut, Bytes, BytesMut}; 3 | use std::convert::From; 4 | use std::io; 5 | use std::str; 6 | 7 | use crate::error::Result; 8 | use crate::parse::*; 9 | 10 | pub(crate) const PACKET_DATA_HEADER_LEN: usize = 4; 11 | 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 13 | #[repr(u16)] 14 | pub(crate) enum PacketType { 15 | Rrq = 1, 16 | Wrq = 2, 17 | Data = 3, 18 | Ack = 4, 19 | Error = 5, 20 | OAck = 6, 21 | } 22 | 23 | /// TFTP protocol error. Should not be confused with `async_tftp::Error`. 24 | #[derive(Debug, Clone)] 25 | pub enum Error { 26 | Msg(String), 27 | UnknownError, 28 | FileNotFound, 29 | PermissionDenied, 30 | DiskFull, 31 | IllegalOperation, 32 | UnknownTransferId, 33 | FileAlreadyExists, 34 | NoSuchUser, 35 | } 36 | 37 | #[derive(Debug)] 38 | pub(crate) enum Packet<'a> { 39 | Rrq(RwReq), 40 | Wrq(RwReq), 41 | Data(u16, &'a [u8]), 42 | Ack(u16), 43 | Error(Error), 44 | OAck(Opts), 45 | } 46 | 47 | #[derive(Debug, PartialEq)] 48 | pub(crate) enum Mode { 49 | Netascii, 50 | Octet, 51 | Mail, 52 | } 53 | 54 | #[derive(Debug, PartialEq)] 55 | pub(crate) struct RwReq { 56 | pub filename: String, 57 | pub mode: Mode, 58 | pub opts: Opts, 59 | } 60 | 61 | #[derive(Debug, Clone, Default, PartialEq)] 62 | pub(crate) struct Opts { 63 | pub block_size: Option, 64 | pub timeout: Option, 65 | pub transfer_size: Option, 66 | } 67 | 68 | impl PacketType { 69 | pub(crate) fn from_u16(n: u16) -> Option { 70 | match n { 71 | 1 => Some(PacketType::Rrq), 72 | 2 => Some(PacketType::Wrq), 73 | 3 => Some(PacketType::Data), 74 | 4 => Some(PacketType::Ack), 75 | 5 => Some(PacketType::Error), 76 | 6 => Some(PacketType::OAck), 77 | _ => None, 78 | } 79 | } 80 | } 81 | 82 | impl From for u16 { 83 | fn from(value: PacketType) -> Self { 84 | value as u16 85 | } 86 | } 87 | 88 | impl<'a> Packet<'a> { 89 | pub(crate) fn decode(data: &[u8]) -> Result { 90 | parse_packet(data) 91 | } 92 | 93 | pub(crate) fn encode(&self, buf: &mut BytesMut) { 94 | match self { 95 | Packet::Rrq(req) => { 96 | buf.put_u16(PacketType::Rrq.into()); 97 | buf.put_slice(req.filename.as_bytes()); 98 | buf.put_u8(0); 99 | buf.put_slice(req.mode.to_str().as_bytes()); 100 | buf.put_u8(0); 101 | req.opts.encode(buf); 102 | } 103 | Packet::Wrq(req) => { 104 | buf.put_u16(PacketType::Wrq.into()); 105 | buf.put_slice(req.filename.as_bytes()); 106 | buf.put_u8(0); 107 | buf.put_slice(req.mode.to_str().as_bytes()); 108 | buf.put_u8(0); 109 | req.opts.encode(buf); 110 | } 111 | Packet::Data(block, data) => { 112 | buf.put_u16(PacketType::Data.into()); 113 | buf.put_u16(*block); 114 | buf.put_slice(data); 115 | } 116 | Packet::Ack(block) => { 117 | buf.put_u16(PacketType::Ack.into()); 118 | buf.put_u16(*block); 119 | } 120 | Packet::Error(error) => { 121 | buf.put_u16(PacketType::Error.into()); 122 | buf.put_u16(error.code()); 123 | buf.put_slice(error.msg().as_bytes()); 124 | buf.put_u8(0); 125 | } 126 | Packet::OAck(opts) => { 127 | buf.put_u16(PacketType::OAck.into()); 128 | opts.encode(buf); 129 | } 130 | } 131 | } 132 | 133 | pub(crate) fn encode_data_head(block_id: u16, buf: &mut BytesMut) { 134 | buf.put_u16(PacketType::Data.into()); 135 | buf.put_u16(block_id); 136 | } 137 | 138 | pub(crate) fn to_bytes(&self) -> Bytes { 139 | let mut buf = BytesMut::new(); 140 | self.encode(&mut buf); 141 | buf.freeze() 142 | } 143 | } 144 | 145 | impl Opts { 146 | fn encode(&self, buf: &mut BytesMut) { 147 | if let Some(block_size) = self.block_size { 148 | buf.put_slice(&b"blksize\0"[..]); 149 | buf.put_slice(block_size.to_string().as_bytes()); 150 | buf.put_u8(0); 151 | } 152 | 153 | if let Some(timeout) = self.timeout { 154 | buf.put_slice(&b"timeout\0"[..]); 155 | buf.put_slice(timeout.to_string().as_bytes()); 156 | buf.put_u8(0); 157 | } 158 | 159 | if let Some(transfer_size) = self.transfer_size { 160 | buf.put_slice(&b"tsize\0"[..]); 161 | buf.put_slice(transfer_size.to_string().as_bytes()); 162 | buf.put_u8(0); 163 | } 164 | } 165 | } 166 | 167 | impl Mode { 168 | pub(crate) fn to_str(&self) -> &'static str { 169 | match self { 170 | Mode::Netascii => "netascii", 171 | Mode::Octet => "octet", 172 | Mode::Mail => "mail", 173 | } 174 | } 175 | } 176 | 177 | impl Error { 178 | pub(crate) fn from_code(code: u16, msg: Option<&str>) -> Self { 179 | #[allow(clippy::wildcard_in_or_patterns)] 180 | match code { 181 | 1 => Error::FileNotFound, 182 | 2 => Error::PermissionDenied, 183 | 3 => Error::DiskFull, 184 | 4 => Error::IllegalOperation, 185 | 5 => Error::UnknownTransferId, 186 | 6 => Error::FileAlreadyExists, 187 | 7 => Error::NoSuchUser, 188 | 0 | _ => match msg { 189 | Some(msg) => Error::Msg(msg.to_string()), 190 | None => Error::UnknownError, 191 | }, 192 | } 193 | } 194 | 195 | pub(crate) fn code(&self) -> u16 { 196 | match self { 197 | Error::Msg(..) => 0, 198 | Error::UnknownError => 0, 199 | Error::FileNotFound => 1, 200 | Error::PermissionDenied => 2, 201 | Error::DiskFull => 3, 202 | Error::IllegalOperation => 4, 203 | Error::UnknownTransferId => 5, 204 | Error::FileAlreadyExists => 6, 205 | Error::NoSuchUser => 7, 206 | } 207 | } 208 | 209 | pub(crate) fn msg(&self) -> &str { 210 | match self { 211 | Error::Msg(msg) => msg, 212 | Error::UnknownError => "Unknown error", 213 | Error::FileNotFound => "File not found", 214 | Error::PermissionDenied => "Permission denied", 215 | Error::DiskFull => "Disk is full", 216 | Error::IllegalOperation => "Illegal operation", 217 | Error::UnknownTransferId => "Unknown transfer ID", 218 | Error::FileAlreadyExists => "File already exists", 219 | Error::NoSuchUser => "No such user", 220 | } 221 | } 222 | } 223 | 224 | impl From for Packet<'_> { 225 | fn from(inner: Error) -> Self { 226 | Packet::Error(inner) 227 | } 228 | } 229 | 230 | impl From for Error { 231 | fn from(io_err: io::Error) -> Self { 232 | match io_err.kind() { 233 | io::ErrorKind::NotFound => Error::FileNotFound, 234 | io::ErrorKind::PermissionDenied => Error::PermissionDenied, 235 | io::ErrorKind::WriteZero => Error::DiskFull, 236 | io::ErrorKind::AlreadyExists => Error::FileAlreadyExists, 237 | _ => match io_err.raw_os_error() { 238 | Some(rc) => Error::Msg(format!("IO error: {}", rc)), 239 | None => Error::UnknownError, 240 | }, 241 | } 242 | } 243 | } 244 | 245 | impl From for Error { 246 | fn from(err: crate::Error) -> Self { 247 | match err { 248 | crate::Error::Packet(e) => e, 249 | crate::Error::Io(e) => e.into(), 250 | crate::Error::InvalidPacket => Error::IllegalOperation, 251 | crate::Error::MaxSendRetriesReached(..) => { 252 | Error::Msg("Max retries reached".to_string()) 253 | } 254 | _ => Error::UnknownError, 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | use std::str::{self, FromStr}; 3 | 4 | use crate::error::{Error, Result}; 5 | use crate::packet::{ 6 | Error as PacketError, Mode, Opts, Packet, PacketType, RwReq, 7 | }; 8 | 9 | pub(crate) fn parse_packet(input: &[u8]) -> Result { 10 | parse_packet_type(input) 11 | .and_then(|(packet_type, data)| match packet_type { 12 | PacketType::Rrq => parse_rrq(data), 13 | PacketType::Wrq => parse_wrq(data), 14 | PacketType::Data => parse_data(data), 15 | PacketType::Ack => parse_ack(data), 16 | PacketType::Error => parse_error(data), 17 | PacketType::OAck => parse_oack(data), 18 | }) 19 | .ok_or(Error::InvalidPacket) 20 | } 21 | 22 | fn parse_nul_str(input: &[u8]) -> Option<(&str, &[u8])> { 23 | let pos = input.iter().position(|c| *c == b'\0')?; 24 | let s = str::from_utf8(&input[..pos]).ok()?; 25 | Some((s, &input[pos + 1..])) 26 | } 27 | 28 | fn parse_u16_be(input: &[u8]) -> Option<(u16, &[u8])> { 29 | let bytes = input.get(..2)?; 30 | let num = u16::from_be_bytes(bytes.try_into().ok()?); 31 | Some((num, &input[2..])) 32 | } 33 | 34 | fn parse_packet_type(input: &[u8]) -> Option<(PacketType, &[u8])> { 35 | let (num, rest) = parse_u16_be(input)?; 36 | let val = PacketType::from_u16(num)?; 37 | Some((val, rest)) 38 | } 39 | 40 | fn parse_mode(input: &[u8]) -> Option<(Mode, &[u8])> { 41 | let (s, rest) = parse_nul_str(input)?; 42 | 43 | let mode = if s.eq_ignore_ascii_case("netascii") { 44 | Mode::Netascii 45 | } else if s.eq_ignore_ascii_case("octet") { 46 | Mode::Octet 47 | } else if s.eq_ignore_ascii_case("mail") { 48 | Mode::Mail 49 | } else { 50 | return None; 51 | }; 52 | 53 | Some((mode, rest)) 54 | } 55 | 56 | pub(crate) fn parse_opts(mut input: &[u8]) -> Option { 57 | let mut opts = Opts::default(); 58 | 59 | while !input.is_empty() { 60 | let (name, rest) = parse_nul_str(input)?; 61 | let (val, rest) = parse_nul_str(rest)?; 62 | 63 | if name.eq_ignore_ascii_case("blksize") { 64 | if let Ok(val) = u16::from_str(val) { 65 | if (8..=65464).contains(&val) { 66 | opts.block_size = Some(val); 67 | } 68 | } 69 | } else if name.eq_ignore_ascii_case("timeout") { 70 | if let Ok(val) = u8::from_str(val) { 71 | if val >= 1 { 72 | opts.timeout = Some(val); 73 | } 74 | } 75 | } else if name.eq_ignore_ascii_case("tsize") { 76 | if let Ok(val) = u64::from_str(val) { 77 | opts.transfer_size = Some(val); 78 | } 79 | } 80 | 81 | input = rest; 82 | } 83 | 84 | Some(opts) 85 | } 86 | 87 | fn parse_rrq(input: &[u8]) -> Option { 88 | let (filename, rest) = parse_nul_str(input)?; 89 | let (mode, rest) = parse_mode(rest)?; 90 | let opts = parse_opts(rest)?; 91 | 92 | Some(Packet::Rrq(RwReq { 93 | filename: filename.to_owned(), 94 | mode, 95 | opts, 96 | })) 97 | } 98 | 99 | fn parse_wrq(input: &[u8]) -> Option { 100 | let (filename, rest) = parse_nul_str(input)?; 101 | let (mode, rest) = parse_mode(rest)?; 102 | let opts = parse_opts(rest)?; 103 | 104 | Some(Packet::Wrq(RwReq { 105 | filename: filename.to_owned(), 106 | mode, 107 | opts, 108 | })) 109 | } 110 | 111 | fn parse_data(input: &[u8]) -> Option { 112 | let (block_nr, rest) = parse_u16_be(input)?; 113 | Some(Packet::Data(block_nr, rest)) 114 | } 115 | 116 | fn parse_ack(input: &[u8]) -> Option { 117 | let (block_nr, rest) = parse_u16_be(input)?; 118 | 119 | if !rest.is_empty() { 120 | return None; 121 | } 122 | 123 | Some(Packet::Ack(block_nr)) 124 | } 125 | 126 | fn parse_error(input: &[u8]) -> Option { 127 | let (code, rest) = parse_u16_be(input)?; 128 | let (msg, rest) = parse_nul_str(rest)?; 129 | 130 | if !rest.is_empty() { 131 | return None; 132 | } 133 | 134 | Some(Packet::Error(PacketError::from_code(code, Some(msg)))) 135 | } 136 | 137 | fn parse_oack(input: &[u8]) -> Option { 138 | let opts = parse_opts(input)?; 139 | Some(Packet::OAck(opts)) 140 | } 141 | 142 | #[cfg(test)] 143 | mod tests { 144 | use super::*; 145 | 146 | #[test] 147 | fn nul_str() { 148 | let (s, rest) = parse_nul_str(b"123\0").unwrap(); 149 | assert_eq!(s, "123"); 150 | assert!(rest.is_empty()); 151 | 152 | let (s, rest) = parse_nul_str(b"123\0\0").unwrap(); 153 | assert_eq!(s, "123"); 154 | assert_eq!(rest, b"\0"); 155 | 156 | let (s1, rest) = parse_nul_str(b"123\0abc\0\xff\xff").unwrap(); 157 | let (s2, rest) = parse_nul_str(rest).unwrap(); 158 | assert_eq!(s1, "123"); 159 | assert_eq!(s2, "abc"); 160 | assert_eq!(rest, b"\xff\xff"); 161 | 162 | let (s1, rest) = parse_nul_str(b"\0\0").unwrap(); 163 | let (s2, rest) = parse_nul_str(rest).unwrap(); 164 | assert_eq!(s1, ""); 165 | assert_eq!(s2, ""); 166 | assert!(rest.is_empty()); 167 | 168 | assert!(parse_nul_str(b"").is_none()); 169 | assert!(parse_nul_str(b"123").is_none()); 170 | assert!(parse_nul_str(b"123\xff\xff\0").is_none()); 171 | } 172 | 173 | #[test] 174 | fn u16_be() { 175 | let (n, rest) = parse_u16_be(b"\x11\x22").unwrap(); 176 | assert_eq!(n, 0x1122); 177 | assert!(rest.is_empty()); 178 | 179 | let (n, rest) = parse_u16_be(b"\x11\x22\x33").unwrap(); 180 | assert_eq!(n, 0x1122); 181 | assert_eq!(rest, b"\x33"); 182 | 183 | let (n1, rest) = parse_u16_be(b"\x11\x22\x33\x44\x55").unwrap(); 184 | let (n2, rest) = parse_u16_be(rest).unwrap(); 185 | assert_eq!(n1, 0x1122); 186 | assert_eq!(n2, 0x3344); 187 | assert_eq!(rest, b"\x55"); 188 | 189 | assert!(parse_u16_be(b"").is_none()); 190 | assert!(parse_u16_be(b"\x11").is_none()); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/server/builder.rs: -------------------------------------------------------------------------------- 1 | use async_executor::Executor; 2 | use async_io::Async; 3 | use async_lock::Mutex; 4 | use std::collections::HashSet; 5 | use std::net::{SocketAddr, UdpSocket}; 6 | use std::path::Path; 7 | use std::sync::Arc; 8 | use std::time::Duration; 9 | 10 | use super::handlers::{DirHandler, DirHandlerMode}; 11 | use super::{Handler, ServerConfig, TftpServer}; 12 | use crate::error::{Error, Result}; 13 | 14 | /// TFTP server builder. 15 | pub struct TftpServerBuilder { 16 | handle: H, 17 | addr: SocketAddr, 18 | socket: Option>, 19 | timeout: Duration, 20 | block_size_limit: Option, 21 | max_send_retries: u32, 22 | ignore_client_timeout: bool, 23 | ignore_client_block_size: bool, 24 | } 25 | 26 | impl TftpServerBuilder { 27 | /// Create new buidler with [`DirHandler`] that serves only read requests. 28 | /// 29 | /// [`DirHandler`]: handlers/struct.DirHandler.html 30 | pub fn with_dir_ro

(dir: P) -> Result> 31 | where 32 | P: AsRef, 33 | { 34 | let handler = DirHandler::new(dir, DirHandlerMode::ReadOnly)?; 35 | Ok(TftpServerBuilder::with_handler(handler)) 36 | } 37 | 38 | /// Create new buidler with [`DirHandler`] that serves only write requests. 39 | pub fn with_dir_wo

(dir: P) -> Result> 40 | where 41 | P: AsRef, 42 | { 43 | let handler = DirHandler::new(dir, DirHandlerMode::WriteOnly)?; 44 | Ok(TftpServerBuilder::with_handler(handler)) 45 | } 46 | 47 | /// Create new buidler with [`DirHandler`] that serves read and write requests. 48 | pub fn with_dir_rw

(dir: P) -> Result> 49 | where 50 | P: AsRef, 51 | { 52 | let handler = DirHandler::new(dir, DirHandlerMode::ReadWrite)?; 53 | Ok(TftpServerBuilder::with_handler(handler)) 54 | } 55 | } 56 | 57 | impl TftpServerBuilder { 58 | /// Create new builder with custom [`Handler`]. 59 | pub fn with_handler(handler: H) -> Self { 60 | TftpServerBuilder { 61 | handle: handler, 62 | addr: "0.0.0.0:69".parse().unwrap(), 63 | socket: None, 64 | timeout: Duration::from_secs(3), 65 | block_size_limit: None, 66 | max_send_retries: 100, 67 | ignore_client_timeout: false, 68 | ignore_client_block_size: false, 69 | } 70 | } 71 | 72 | /// Set listening address. 73 | /// 74 | /// This is ignored if underling socket is set. 75 | /// 76 | /// **Default:** `0.0.0.0:69` 77 | pub fn bind(self, addr: SocketAddr) -> Self { 78 | TftpServerBuilder { 79 | addr, 80 | ..self 81 | } 82 | } 83 | 84 | /// Set underling UDP socket. 85 | pub fn socket(self, socket: Async) -> Self { 86 | TftpServerBuilder { 87 | socket: Some(socket), 88 | ..self 89 | } 90 | } 91 | 92 | /// Set underling UDP socket. 93 | pub fn std_socket(self, socket: UdpSocket) -> Result { 94 | let socket = Async::new(socket)?; 95 | 96 | Ok(TftpServerBuilder { 97 | socket: Some(socket), 98 | ..self 99 | }) 100 | } 101 | 102 | /// Set retry timeout. 103 | /// 104 | /// Client can override this (RFC2349). If you want to enforce it you must 105 | /// combine it [`ignore_client_timeout`](Self::ignore_client_timeout). 106 | /// 107 | /// This crate allows you to set non-standard timeouts (i.e. timeouts that are less 108 | /// than a second). However if you choose to do it make sure you test it well in your 109 | /// environment since client's behavior is undefined. 110 | /// 111 | /// **Default:** 3 seconds 112 | pub fn timeout(self, timeout: Duration) -> Self { 113 | TftpServerBuilder { 114 | timeout, 115 | ..self 116 | } 117 | } 118 | 119 | /// Set maximum block size. 120 | /// 121 | /// Client can request a specific block size (RFC2348). Use this option if you 122 | /// want to set a limit. 123 | /// 124 | /// **Real life scenario:** U-Boot does not support IP fragmentation and requests 125 | /// block size of 1468. This works fine if your MTU is 1500 bytes, however if 126 | /// you are accessing client through a VPN, then transfer will never start. Use 127 | /// this option to workaround the problem. 128 | pub fn block_size_limit(self, size: u16) -> Self { 129 | TftpServerBuilder { 130 | block_size_limit: Some(size), 131 | ..self 132 | } 133 | } 134 | 135 | /// Set maximum send retries for a data block. 136 | /// 137 | /// On timeout server will try to send the data block again. When retries are 138 | /// reached for the specific data block the server closes the connection with 139 | /// the client. 140 | /// 141 | /// Default: 100 retries. 142 | pub fn max_send_retries(self, retries: u32) -> Self { 143 | TftpServerBuilder { 144 | max_send_retries: retries, 145 | ..self 146 | } 147 | } 148 | 149 | /// Ignore client's `timeout` option. 150 | /// 151 | /// With this you enforce server's timeout by ignoring client's 152 | /// `timeout` option of RFC2349. 153 | pub fn ignore_client_timeout(self) -> Self { 154 | TftpServerBuilder { 155 | ignore_client_timeout: true, 156 | ..self 157 | } 158 | } 159 | 160 | /// Ignore client's block size option. 161 | /// 162 | /// With this you can ignore client's `blksize` option of RFC2348. 163 | /// This will enforce 512 block size that is defined in RFC1350. 164 | pub fn ignore_client_block_size(self) -> Self { 165 | TftpServerBuilder { 166 | ignore_client_block_size: true, 167 | ..self 168 | } 169 | } 170 | 171 | /// Build [`TftpServer`]. 172 | pub async fn build(mut self) -> Result> { 173 | let socket = match self.socket.take() { 174 | Some(socket) => socket, 175 | None => Async::::bind(self.addr).map_err(Error::Bind)?, 176 | }; 177 | 178 | let config = ServerConfig { 179 | timeout: self.timeout, 180 | block_size_limit: self.block_size_limit, 181 | max_send_retries: self.max_send_retries, 182 | ignore_client_timeout: self.ignore_client_timeout, 183 | ignore_client_block_size: self.ignore_client_block_size, 184 | }; 185 | 186 | let local_ip = socket.as_ref().local_addr()?.ip(); 187 | Ok(TftpServer { 188 | socket, 189 | handler: Arc::new(Mutex::new(self.handle)), 190 | reqs_in_progress: Arc::new(Mutex::new(HashSet::new())), 191 | ex: Executor::new(), 192 | config, 193 | local_ip, 194 | }) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/server/handler.rs: -------------------------------------------------------------------------------- 1 | use futures_lite::{AsyncRead, AsyncWrite}; 2 | use std::future::Future; 3 | use std::net::SocketAddr; 4 | use std::path::Path; 5 | 6 | use crate::packet; 7 | 8 | /// Trait for implementing advance handlers. 9 | pub trait Handler: Send { 10 | type Reader: AsyncRead + Unpin + Send + 'static; 11 | type Writer: AsyncWrite + Unpin + Send + 'static; 12 | 13 | /// Open `Reader` to serve a read request. 14 | fn read_req_open( 15 | &mut self, 16 | client: &SocketAddr, 17 | path: &Path, 18 | ) -> impl Future), packet::Error>> 19 | + Send; 20 | 21 | /// Open `Writer` to serve a write request. 22 | fn write_req_open( 23 | &mut self, 24 | client: &SocketAddr, 25 | path: &Path, 26 | size: Option, 27 | ) -> impl Future> + Send; 28 | } 29 | -------------------------------------------------------------------------------- /src/server/handlers/dir.rs: -------------------------------------------------------------------------------- 1 | use blocking::{unblock, Unblock}; 2 | use log::trace; 3 | use std::fs::{self, File}; 4 | use std::io; 5 | use std::net::SocketAddr; 6 | use std::path::Component; 7 | use std::path::{Path, PathBuf}; 8 | 9 | use crate::error::{Error, Result}; 10 | use crate::packet; 11 | 12 | /// Handler that serves read requests for a directory. 13 | pub struct DirHandler { 14 | dir: PathBuf, 15 | serve_rrq: bool, 16 | serve_wrq: bool, 17 | } 18 | 19 | pub enum DirHandlerMode { 20 | /// Serve only read requests. 21 | ReadOnly, 22 | /// Serve only write requests. 23 | WriteOnly, 24 | /// Server read and write requests. 25 | ReadWrite, 26 | } 27 | 28 | impl DirHandler { 29 | /// Create new handler for directory. 30 | pub fn new

(dir: P, flags: DirHandlerMode) -> Result 31 | where 32 | P: AsRef, 33 | { 34 | let dir = fs::canonicalize(dir.as_ref())?; 35 | 36 | if !dir.is_dir() { 37 | return Err(Error::NotDir(dir)); 38 | } 39 | 40 | trace!("TFTP directory: {}", dir.display()); 41 | 42 | let serve_rrq = match flags { 43 | DirHandlerMode::ReadOnly => true, 44 | DirHandlerMode::WriteOnly => false, 45 | DirHandlerMode::ReadWrite => true, 46 | }; 47 | 48 | let serve_wrq = match flags { 49 | DirHandlerMode::ReadOnly => false, 50 | DirHandlerMode::WriteOnly => true, 51 | DirHandlerMode::ReadWrite => true, 52 | }; 53 | 54 | Ok(DirHandler { 55 | dir, 56 | serve_rrq, 57 | serve_wrq, 58 | }) 59 | } 60 | } 61 | 62 | impl crate::server::Handler for DirHandler { 63 | type Reader = Unblock; 64 | type Writer = Unblock; 65 | 66 | async fn read_req_open( 67 | &mut self, 68 | _client: &SocketAddr, 69 | path: &Path, 70 | ) -> Result<(Self::Reader, Option), packet::Error> { 71 | if !self.serve_rrq { 72 | return Err(packet::Error::IllegalOperation); 73 | } 74 | 75 | let path = secure_path(&self.dir, path)?; 76 | 77 | // Send only regular files 78 | if !path.is_file() { 79 | return Err(packet::Error::FileNotFound); 80 | } 81 | 82 | let path_clone = path.clone(); 83 | let (file, len) = unblock(move || open_file_ro(path_clone)).await?; 84 | let reader = Unblock::new(file); 85 | 86 | trace!("TFTP sending file: {}", path.display()); 87 | 88 | Ok((reader, len)) 89 | } 90 | 91 | async fn write_req_open( 92 | &mut self, 93 | _client: &SocketAddr, 94 | path: &Path, 95 | size: Option, 96 | ) -> Result { 97 | if !self.serve_wrq { 98 | return Err(packet::Error::IllegalOperation); 99 | } 100 | 101 | let path = secure_path(&self.dir, path)?; 102 | 103 | let path_clone = path.clone(); 104 | let file = unblock(move || open_file_wo(path_clone, size)).await?; 105 | let writer = Unblock::new(file); 106 | 107 | trace!("TFTP receiving file: {}", path.display()); 108 | 109 | Ok(writer) 110 | } 111 | } 112 | 113 | fn secure_path( 114 | restricted_dir: &Path, 115 | path: &Path, 116 | ) -> Result { 117 | // Strip `/` and `./` prefixes 118 | let path = path 119 | .strip_prefix("/") 120 | .or_else(|_| path.strip_prefix("./")) 121 | .unwrap_or(path); 122 | 123 | // Avoid directory traversal attack by filtering `../`. 124 | if path.components().any(|x| x == Component::ParentDir) { 125 | return Err(packet::Error::PermissionDenied); 126 | } 127 | 128 | // Path should not start from root dir or have any Windows prefixes. 129 | // i.e. We accept only normal path components. 130 | match path.components().next() { 131 | Some(Component::Normal(_)) => {} 132 | _ => return Err(packet::Error::PermissionDenied), 133 | } 134 | 135 | Ok(restricted_dir.join(path)) 136 | } 137 | 138 | fn open_file_ro(path: PathBuf) -> io::Result<(File, Option)> { 139 | let file = File::open(path)?; 140 | let len = file.metadata().ok().map(|m| m.len()); 141 | Ok((file, len)) 142 | } 143 | 144 | fn open_file_wo(path: PathBuf, size: Option) -> io::Result { 145 | let file = File::create(path)?; 146 | 147 | if let Some(size) = size { 148 | file.set_len(size)?; 149 | } 150 | 151 | Ok(file) 152 | } 153 | -------------------------------------------------------------------------------- /src/server/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | //! Handlers for common use-cases. 2 | 3 | mod dir; 4 | 5 | pub use self::dir::*; 6 | -------------------------------------------------------------------------------- /src/server/mod.rs: -------------------------------------------------------------------------------- 1 | //! Server side implementation. 2 | 3 | mod builder; 4 | mod handler; 5 | mod read_req; 6 | #[allow(clippy::module_inception)] 7 | mod server; 8 | mod write_req; 9 | 10 | pub mod handlers; 11 | 12 | pub use self::builder::*; 13 | pub use self::handler::*; 14 | pub use self::server::*; 15 | -------------------------------------------------------------------------------- /src/server/read_req.rs: -------------------------------------------------------------------------------- 1 | use async_io::Async; 2 | use bytes::{BufMut, Bytes, BytesMut}; 3 | use futures_lite::{AsyncRead, AsyncReadExt}; 4 | use log::trace; 5 | use std::cmp; 6 | use std::io; 7 | use std::net::{IpAddr, SocketAddr, UdpSocket}; 8 | use std::slice; 9 | use std::time::Duration; 10 | 11 | use crate::error::{Error, Result}; 12 | use crate::packet::{Opts, Packet, RwReq, PACKET_DATA_HEADER_LEN}; 13 | use crate::server::{ServerConfig, DEFAULT_BLOCK_SIZE}; 14 | use crate::utils::io_timeout; 15 | 16 | pub(crate) struct ReadRequest<'r, R> 17 | where 18 | R: AsyncRead + Send, 19 | { 20 | peer: SocketAddr, 21 | socket: Async, 22 | reader: &'r mut R, 23 | buffer: BytesMut, 24 | block_size: usize, 25 | timeout: Duration, 26 | max_send_retries: u32, 27 | oack_opts: Option, 28 | } 29 | 30 | impl<'r, R> ReadRequest<'r, R> 31 | where 32 | R: AsyncRead + Send + Unpin, 33 | { 34 | pub(crate) async fn init( 35 | reader: &'r mut R, 36 | file_size: Option, 37 | peer: SocketAddr, 38 | req: &RwReq, 39 | config: ServerConfig, 40 | local_ip: IpAddr, 41 | ) -> Result> { 42 | let oack_opts = build_oack_opts(&config, req, file_size); 43 | 44 | let block_size = oack_opts 45 | .as_ref() 46 | .and_then(|o| o.block_size) 47 | .map(usize::from) 48 | .unwrap_or(DEFAULT_BLOCK_SIZE); 49 | 50 | let timeout = oack_opts 51 | .as_ref() 52 | .and_then(|o| o.timeout) 53 | .map(|t| Duration::from_secs(u64::from(t))) 54 | .unwrap_or(config.timeout); 55 | 56 | let addr = SocketAddr::new(local_ip, 0); 57 | let socket = Async::::bind(addr).map_err(Error::Bind)?; 58 | 59 | Ok(ReadRequest { 60 | peer, 61 | socket, 62 | reader, 63 | buffer: BytesMut::with_capacity( 64 | PACKET_DATA_HEADER_LEN + block_size, 65 | ), 66 | block_size, 67 | timeout, 68 | max_send_retries: config.max_send_retries, 69 | oack_opts, 70 | }) 71 | } 72 | 73 | pub(crate) async fn handle(&mut self) { 74 | if let Err(e) = self.try_handle().await { 75 | trace!("RRQ request failed (peer: {}, error: {})", &self.peer, &e); 76 | 77 | Packet::Error(e.into()).encode(&mut self.buffer); 78 | let buf = self.buffer.split().freeze(); 79 | // Errors are never retransmitted. 80 | // We do not care if `send_to` resulted to an IO error. 81 | let _ = self.socket.send_to(&buf[..], self.peer).await; 82 | } 83 | } 84 | 85 | async fn try_handle(&mut self) -> Result<()> { 86 | let mut block_id: u16 = 0; 87 | 88 | // Send file to client 89 | loop { 90 | let is_last_block; 91 | 92 | // Reclaim buffer 93 | self.buffer.reserve(PACKET_DATA_HEADER_LEN + self.block_size); 94 | 95 | // Encode head of Data packet 96 | block_id = block_id.wrapping_add(1); 97 | Packet::encode_data_head(block_id, &mut self.buffer); 98 | 99 | // Read block in self.buffer 100 | let buf = unsafe { 101 | let uninit_buf = self.buffer.chunk_mut(); 102 | 103 | let data_buf = slice::from_raw_parts_mut( 104 | uninit_buf.as_mut_ptr(), 105 | uninit_buf.len(), 106 | ); 107 | 108 | let len = self.read_block(data_buf).await?; 109 | is_last_block = len < self.block_size; 110 | 111 | self.buffer.advance_mut(len); 112 | self.buffer.split().freeze() 113 | }; 114 | 115 | // Send OACK after we manage to read the first block from reader. 116 | // 117 | // We do this because we want to give the developers the option to 118 | // produce an error after they construct a reader. 119 | if let Some(opts) = self.oack_opts.take() { 120 | trace!("RRQ OACK (peer: {}, opts: {:?}", &self.peer, &opts); 121 | 122 | let mut buf = BytesMut::new(); 123 | Packet::OAck(opts.to_owned()).encode(&mut buf); 124 | 125 | self.send(buf.split().freeze(), 0).await?; 126 | } 127 | 128 | // Send Data packet 129 | self.send(buf, block_id).await?; 130 | 131 | if is_last_block { 132 | break; 133 | } 134 | } 135 | 136 | trace!("RRQ request served (peer: {})", &self.peer); 137 | Ok(()) 138 | } 139 | 140 | async fn send(&mut self, packet: Bytes, block_id: u16) -> Result<()> { 141 | // Send packet until we receive an ack 142 | for _ in 0..=self.max_send_retries { 143 | self.socket.send_to(&packet[..], self.peer).await?; 144 | 145 | match self.recv_ack(block_id).await { 146 | Ok(_) => { 147 | trace!( 148 | "RRQ (peer: {}, block_id: {}) - Received ACK", 149 | &self.peer, 150 | block_id 151 | ); 152 | return Ok(()); 153 | } 154 | Err(ref e) if e.kind() == io::ErrorKind::TimedOut => { 155 | trace!( 156 | "RRQ (peer: {}, block_id: {}) - Timeout", 157 | &self.peer, 158 | block_id 159 | ); 160 | continue; 161 | } 162 | Err(e) => return Err(e.into()), 163 | } 164 | } 165 | 166 | Err(Error::MaxSendRetriesReached(self.peer, block_id)) 167 | } 168 | 169 | async fn recv_ack(&mut self, block_id: u16) -> io::Result<()> { 170 | // We can not use `self` within `async_std::io::timeout` because not all 171 | // struct members implement `Sync`. So we borrow only what we need. 172 | let socket = &mut self.socket; 173 | let peer = self.peer; 174 | 175 | io_timeout(self.timeout, async { 176 | let mut buf = [0u8; 1024]; 177 | 178 | loop { 179 | let (len, recved_peer) = socket.recv_from(&mut buf[..]).await?; 180 | 181 | // if the packet do not come from the client we are serving, then ignore it 182 | if recved_peer != peer { 183 | continue; 184 | } 185 | 186 | // parse only valid Ack packets, the rest are ignored 187 | if let Ok(Packet::Ack(recved_block_id)) = 188 | Packet::decode(&buf[..len]) 189 | { 190 | if recved_block_id == block_id { 191 | return Ok(()); 192 | } 193 | } 194 | } 195 | }) 196 | .await?; 197 | 198 | Ok(()) 199 | } 200 | 201 | async fn read_block(&mut self, buf: &mut [u8]) -> Result { 202 | let mut len = 0; 203 | 204 | while len < buf.len() { 205 | match self.reader.read(&mut buf[len..]).await? { 206 | 0 => break, 207 | x => len += x, 208 | } 209 | } 210 | 211 | Ok(len) 212 | } 213 | } 214 | 215 | fn build_oack_opts( 216 | config: &ServerConfig, 217 | req: &RwReq, 218 | file_size: Option, 219 | ) -> Option { 220 | let mut opts = Opts::default(); 221 | 222 | if !config.ignore_client_block_size { 223 | opts.block_size = match (req.opts.block_size, config.block_size_limit) { 224 | (Some(bsize), Some(limit)) => Some(cmp::min(bsize, limit)), 225 | (Some(bsize), None) => Some(bsize), 226 | _ => None, 227 | }; 228 | } 229 | 230 | if !config.ignore_client_timeout { 231 | opts.timeout = req.opts.timeout; 232 | } 233 | 234 | if let (Some(0), Some(file_size)) = (req.opts.transfer_size, file_size) { 235 | opts.transfer_size = Some(file_size); 236 | } 237 | 238 | if opts == Opts::default() { 239 | None 240 | } else { 241 | Some(opts) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/server/server.rs: -------------------------------------------------------------------------------- 1 | use async_executor::Executor; 2 | use async_io::Async; 3 | use async_lock::Mutex; 4 | use log::trace; 5 | use std::collections::HashSet; 6 | use std::future::Future; 7 | use std::net::{IpAddr, SocketAddr, UdpSocket}; 8 | use std::sync::Arc; 9 | use std::time::Duration; 10 | 11 | use super::read_req::*; 12 | use super::write_req::*; 13 | use super::Handler; 14 | use crate::error::*; 15 | use crate::packet::{Packet, RwReq}; 16 | 17 | /// TFTP server. 18 | pub struct TftpServer 19 | where 20 | H: Handler, 21 | { 22 | pub(crate) socket: Async, 23 | pub(crate) handler: Arc>, 24 | pub(crate) reqs_in_progress: Arc>>, 25 | pub(crate) ex: Executor<'static>, 26 | pub(crate) config: ServerConfig, 27 | pub(crate) local_ip: IpAddr, 28 | } 29 | 30 | #[derive(Clone)] 31 | pub(crate) struct ServerConfig { 32 | pub(crate) timeout: Duration, 33 | pub(crate) block_size_limit: Option, 34 | pub(crate) max_send_retries: u32, 35 | pub(crate) ignore_client_timeout: bool, 36 | pub(crate) ignore_client_block_size: bool, 37 | } 38 | 39 | pub(crate) const DEFAULT_BLOCK_SIZE: usize = 512; 40 | 41 | impl TftpServer 42 | where 43 | H: Handler, 44 | { 45 | /// Returns the listenning socket address. 46 | pub fn listen_addr(&self) -> Result { 47 | Ok(self.socket.get_ref().local_addr()?) 48 | } 49 | 50 | /// Consume and start the server. 51 | pub async fn serve(self) -> Result<()> { 52 | self.ex 53 | .run(async { 54 | let mut buf = [0u8; 4096]; 55 | 56 | loop { 57 | let (len, peer) = self.socket.recv_from(&mut buf).await?; 58 | self.handle_req_packet(peer, &buf[..len]).await; 59 | } 60 | }) 61 | .await 62 | } 63 | 64 | async fn handle_req_packet(&self, peer: SocketAddr, data: &[u8]) { 65 | let packet = match Packet::decode(data) { 66 | Ok(p @ Packet::Rrq(_)) => p, 67 | Ok(p @ Packet::Wrq(_)) => p, 68 | // Ignore packets that are not requests 69 | Ok(_) => return, 70 | // Ignore invalid packets 71 | Err(_) => return, 72 | }; 73 | 74 | if !self.reqs_in_progress.lock().await.insert(peer) { 75 | // Ignore pending requests 76 | return; 77 | } 78 | 79 | match packet { 80 | Packet::Rrq(req) => self.handle_rrq(peer, req), 81 | Packet::Wrq(req) => self.handle_wrq(peer, req), 82 | _ => unreachable!(), 83 | } 84 | } 85 | 86 | fn handle_rrq(&self, peer: SocketAddr, req: RwReq) { 87 | trace!("RRQ recieved (peer: {}, req: {:?})", &peer, &req); 88 | 89 | let handler = Arc::clone(&self.handler); 90 | let config = self.config.clone(); 91 | let local_ip = self.local_ip; 92 | 93 | // Prepare request future 94 | let req_fut = async move { 95 | let (mut reader, size) = handler 96 | .lock() 97 | .await 98 | .read_req_open(&peer, req.filename.as_ref()) 99 | .await 100 | .map_err(Error::Packet)?; 101 | 102 | let mut read_req = ReadRequest::init( 103 | &mut reader, 104 | size, 105 | peer, 106 | &req, 107 | config, 108 | local_ip, 109 | ) 110 | .await?; 111 | 112 | read_req.handle().await; 113 | 114 | Ok(()) 115 | }; 116 | 117 | let reqs_in_progress = Arc::clone(&self.reqs_in_progress); 118 | 119 | // Run request future in a new task 120 | self.ex 121 | .spawn(run_req(req_fut, peer, reqs_in_progress, local_ip)) 122 | .detach(); 123 | } 124 | 125 | fn handle_wrq(&self, peer: SocketAddr, req: RwReq) { 126 | trace!("WRQ recieved (peer: {}, req: {:?})", &peer, &req); 127 | 128 | let handler = Arc::clone(&self.handler); 129 | let config = self.config.clone(); 130 | let local_ip = self.local_ip; 131 | 132 | // Prepare request future 133 | let req_fut = async move { 134 | let mut writer = handler 135 | .lock() 136 | .await 137 | .write_req_open( 138 | &peer, 139 | req.filename.as_ref(), 140 | req.opts.transfer_size, 141 | ) 142 | .await 143 | .map_err(Error::Packet)?; 144 | 145 | let mut write_req = 146 | WriteRequest::init(&mut writer, peer, &req, config, local_ip) 147 | .await?; 148 | 149 | write_req.handle().await; 150 | 151 | Ok(()) 152 | }; 153 | 154 | let reqs_in_progress = Arc::clone(&self.reqs_in_progress); 155 | 156 | // Run request future in a new task 157 | self.ex 158 | .spawn(run_req(req_fut, peer, reqs_in_progress, local_ip)) 159 | .detach(); 160 | } 161 | } 162 | 163 | async fn send_error( 164 | error: Error, 165 | peer: SocketAddr, 166 | local_ip: IpAddr, 167 | ) -> Result<()> { 168 | let addr: SocketAddr = SocketAddr::new(local_ip, 0); 169 | let socket = Async::::bind(addr).map_err(Error::Bind)?; 170 | 171 | let data = Packet::Error(error.into()).to_bytes(); 172 | socket.send_to(&data[..], peer).await?; 173 | 174 | Ok(()) 175 | } 176 | 177 | async fn run_req( 178 | req_fut: impl Future>, 179 | peer: SocketAddr, 180 | reqs_in_progress: Arc>>, 181 | local_ip: IpAddr, 182 | ) { 183 | if let Err(e) = req_fut.await { 184 | trace!("Request failed (peer: {}, error: {}", &peer, &e); 185 | 186 | if let Err(e) = send_error(e, peer, local_ip).await { 187 | trace!("Failed to send error to peer {}: {}", &peer, &e); 188 | } 189 | } 190 | 191 | reqs_in_progress.lock().await.remove(&peer); 192 | } 193 | -------------------------------------------------------------------------------- /src/server/write_req.rs: -------------------------------------------------------------------------------- 1 | use async_io::Async; 2 | use bytes::{Buf, Bytes, BytesMut}; 3 | use futures_lite::{AsyncWrite, AsyncWriteExt}; 4 | use log::trace; 5 | use std::cmp; 6 | use std::io; 7 | use std::net::{IpAddr, SocketAddr, UdpSocket}; 8 | use std::time::Duration; 9 | 10 | use crate::error::{Error, Result}; 11 | use crate::packet::{Opts, Packet, RwReq, PACKET_DATA_HEADER_LEN}; 12 | use crate::server::{ServerConfig, DEFAULT_BLOCK_SIZE}; 13 | use crate::utils::io_timeout; 14 | 15 | pub(crate) struct WriteRequest<'w, W> 16 | where 17 | W: AsyncWrite + Send, 18 | { 19 | peer: SocketAddr, 20 | socket: Async, 21 | writer: &'w mut W, 22 | // BytesMut reclaims memory only if it is continuous. 23 | // Because we always need to keep the previous ACK, we can not use 24 | // `buffer` as its storage since it breaks the continuity. 25 | // So we keep previous ACK in `ack` buffer. 26 | buffer: BytesMut, 27 | ack: BytesMut, 28 | block_size: usize, 29 | timeout: Duration, 30 | max_retries: u32, 31 | oack_opts: Option, 32 | } 33 | 34 | impl<'w, W> WriteRequest<'w, W> 35 | where 36 | W: AsyncWrite + Send + Unpin, 37 | { 38 | pub(crate) async fn init( 39 | writer: &'w mut W, 40 | peer: SocketAddr, 41 | req: &RwReq, 42 | config: ServerConfig, 43 | local_ip: IpAddr, 44 | ) -> Result> { 45 | let oack_opts = build_oack_opts(&config, req); 46 | 47 | let block_size = oack_opts 48 | .as_ref() 49 | .and_then(|o| o.block_size) 50 | .map(usize::from) 51 | .unwrap_or(DEFAULT_BLOCK_SIZE); 52 | 53 | let timeout = oack_opts 54 | .as_ref() 55 | .and_then(|o| o.timeout) 56 | .map(|t| Duration::from_secs(u64::from(t))) 57 | .unwrap_or(config.timeout); 58 | 59 | let addr = SocketAddr::new(local_ip, 0); 60 | let socket = Async::::bind(addr).map_err(Error::Bind)?; 61 | 62 | Ok(WriteRequest { 63 | peer, 64 | socket, 65 | writer, 66 | buffer: BytesMut::new(), 67 | ack: BytesMut::new(), 68 | block_size, 69 | timeout, 70 | max_retries: config.max_send_retries, 71 | oack_opts, 72 | }) 73 | } 74 | 75 | pub(crate) async fn handle(&mut self) { 76 | if let Err(e) = self.try_handle().await { 77 | trace!("WRQ request failed (peer: {}, error: {}", self.peer, &e); 78 | 79 | Packet::Error(e.into()).encode(&mut self.buffer); 80 | let buf = self.buffer.split().freeze(); 81 | // Errors are never retransmitted. 82 | // We do not care if `send_to` resulted to an IO error. 83 | let _ = self.socket.send_to(&buf[..], self.peer).await; 84 | } 85 | } 86 | 87 | async fn try_handle(&mut self) -> Result<()> { 88 | let mut block_id: u16 = 0; 89 | 90 | // Send first Ack/OAck 91 | match self.oack_opts.take() { 92 | Some(opts) => Packet::OAck(opts).encode(&mut self.ack), 93 | None => Packet::Ack(0).encode(&mut self.ack), 94 | } 95 | 96 | self.socket.send_to(&self.ack, self.peer).await?; 97 | 98 | loop { 99 | // Recv data 100 | block_id = block_id.wrapping_add(1); 101 | let data = self.recv_data(block_id).await?; 102 | 103 | // Write data to file 104 | self.writer.write_all(&data[..]).await?; 105 | 106 | if data.len() < self.block_size { 107 | break; 108 | } 109 | } 110 | 111 | Ok(()) 112 | } 113 | 114 | async fn recv_data(&mut self, block_id: u16) -> Result { 115 | for _ in 0..=self.max_retries { 116 | match self.recv_data_block(block_id).await { 117 | Ok(data) => { 118 | // Data received, send ACK 119 | self.ack.clear(); 120 | Packet::Ack(block_id).encode(&mut self.ack); 121 | 122 | self.socket.send_to(&self.ack, self.peer).await?; 123 | return Ok(data); 124 | } 125 | Err(ref e) if e.kind() == io::ErrorKind::TimedOut => { 126 | // On timeout reply with the previous ACK packet 127 | self.socket.send_to(&self.ack, self.peer).await?; 128 | continue; 129 | } 130 | Err(e) => return Err(e.into()), 131 | } 132 | } 133 | 134 | Err(Error::MaxSendRetriesReached(self.peer, block_id)) 135 | } 136 | 137 | async fn recv_data_block(&mut self, block_id: u16) -> io::Result { 138 | let socket = &mut self.socket; 139 | let peer = self.peer; 140 | 141 | self.buffer.resize(PACKET_DATA_HEADER_LEN + self.block_size, 0); 142 | let mut buf = self.buffer.split(); 143 | 144 | io_timeout(self.timeout, async move { 145 | loop { 146 | let (len, recved_peer) = socket.recv_from(&mut buf[..]).await?; 147 | 148 | if recved_peer != peer { 149 | continue; 150 | } 151 | 152 | if let Ok(Packet::Data(recved_block_id, _)) = 153 | Packet::decode(&buf[..len]) 154 | { 155 | if recved_block_id == block_id { 156 | buf.truncate(len); 157 | buf.advance(PACKET_DATA_HEADER_LEN); 158 | break; 159 | } 160 | } 161 | } 162 | 163 | Ok(buf.freeze()) 164 | }) 165 | .await 166 | } 167 | } 168 | 169 | fn build_oack_opts(config: &ServerConfig, req: &RwReq) -> Option { 170 | let mut opts = Opts::default(); 171 | 172 | if !config.ignore_client_block_size { 173 | opts.block_size = match (req.opts.block_size, config.block_size_limit) { 174 | (Some(bsize), Some(limit)) => Some(cmp::min(bsize, limit)), 175 | (Some(bsize), None) => Some(bsize), 176 | _ => None, 177 | }; 178 | } 179 | 180 | if !config.ignore_client_timeout { 181 | opts.timeout = req.opts.timeout; 182 | } 183 | 184 | opts.transfer_size = req.opts.transfer_size; 185 | 186 | if opts == Opts::default() { 187 | None 188 | } else { 189 | Some(opts) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/tests/external_client.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "external-client-tests")] 2 | #![cfg(target_os = "linux")] 3 | 4 | use std::fs; 5 | use std::io; 6 | use std::net::SocketAddr; 7 | use std::process::Command; 8 | use std::process::Stdio; 9 | use tempfile::tempdir; 10 | 11 | pub fn external_tftp_recv( 12 | filename: &str, 13 | server: SocketAddr, 14 | block_size: Option, 15 | ) -> io::Result { 16 | let tmp = tempdir()?; 17 | let path = tmp.path().join("data"); 18 | 19 | // Expects `atftp` to be installed 20 | let mut cmd = Command::new("atftp"); 21 | 22 | // Redirect output to /dev/null 23 | cmd.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null()); 24 | 25 | if let Some(block_size) = block_size { 26 | cmd.arg("--option").arg(format!("blksize {}", block_size)); 27 | } 28 | 29 | cmd.arg("-g") 30 | .arg("-l") 31 | .arg(&path) 32 | .arg("-r") 33 | .arg(filename) 34 | .arg(server.ip().to_string()) 35 | .arg(server.port().to_string()) 36 | .status() 37 | .expect("atftp is not installed"); 38 | 39 | let data = fs::read(path)?; 40 | Ok(md5::compute(data)) 41 | } 42 | -------------------------------------------------------------------------------- /src/tests/handlers.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "external-client-tests")] 2 | #![cfg(target_os = "linux")] 3 | 4 | use async_channel::Sender; 5 | use futures_lite::io::Sink; 6 | use std::net::SocketAddr; 7 | use std::path::Path; 8 | 9 | use super::random_file::RandomFile; 10 | use crate::packet; 11 | use crate::server::Handler; 12 | 13 | pub struct RandomHandler { 14 | md5_tx: Option>, 15 | file_size: usize, 16 | } 17 | 18 | impl RandomHandler { 19 | pub fn new(file_size: usize, md5_tx: Sender) -> Self { 20 | RandomHandler { 21 | md5_tx: Some(md5_tx), 22 | file_size, 23 | } 24 | } 25 | } 26 | 27 | impl Handler for RandomHandler { 28 | type Reader = RandomFile; 29 | type Writer = Sink; 30 | 31 | async fn read_req_open( 32 | &mut self, 33 | _client: &SocketAddr, 34 | _path: &Path, 35 | ) -> Result<(Self::Reader, Option), packet::Error> { 36 | let md5_tx = self.md5_tx.take().expect("md5_tx already consumed"); 37 | Ok((RandomFile::new(self.file_size, md5_tx), None)) 38 | } 39 | 40 | async fn write_req_open( 41 | &mut self, 42 | _client: &SocketAddr, 43 | _path: &Path, 44 | _size: Option, 45 | ) -> Result { 46 | Err(packet::Error::IllegalOperation) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | mod external_client; 4 | mod handlers; 5 | mod packet; 6 | mod random_file; 7 | mod rrq; 8 | -------------------------------------------------------------------------------- /src/tests/packet.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Bytes, BytesMut}; 2 | 3 | use crate::error::Error; 4 | use crate::packet::{self, Mode, Opts, Packet, RwReq}; 5 | use crate::parse::parse_opts; 6 | 7 | fn packet_to_bytes(packet: &Packet) -> Bytes { 8 | let mut buf = BytesMut::with_capacity(0); 9 | packet.encode(&mut buf); 10 | buf.freeze() 11 | } 12 | 13 | #[test] 14 | fn check_rrq() { 15 | let packet = Packet::decode(b"\x00\x01abc\0netascii\0"); 16 | 17 | assert!(matches!(packet, Ok(Packet::Rrq(ref req)) 18 | if req == &RwReq { 19 | filename: "abc".to_string(), 20 | mode: Mode::Netascii, 21 | opts: Opts::default() 22 | } 23 | )); 24 | 25 | assert_eq!( 26 | packet_to_bytes(&packet.unwrap()), 27 | b"\x00\x01abc\0netascii\0"[..] 28 | ); 29 | 30 | let packet = Packet::decode(b"\x00\x01abc\0netascII\0"); 31 | 32 | assert!(matches!(packet, Ok(Packet::Rrq(ref req)) 33 | if req == &RwReq { 34 | filename: "abc".to_string(), 35 | mode: Mode::Netascii, 36 | opts: Opts::default() 37 | } 38 | )); 39 | 40 | assert_eq!( 41 | packet_to_bytes(&packet.unwrap()), 42 | b"\x00\x01abc\0netascii\0"[..] 43 | ); 44 | 45 | let packet = Packet::decode(b"\x00\x01abc\0netascii\0more"); 46 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 47 | 48 | let packet = Packet::decode(b"\x00\x01abc\0netascii"); 49 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 50 | 51 | let packet = Packet::decode(b"\x00\x01abc\0netascXX\0"); 52 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 53 | 54 | let packet = Packet::decode( 55 | b"\x00\x01abc\0netascii\0blksize\0123\0timeout\03\0tsize\05556\0", 56 | ); 57 | 58 | assert!(matches!(packet, Ok(Packet::Rrq(ref req)) 59 | if req == &RwReq { 60 | filename: "abc".to_string(), 61 | mode: Mode::Netascii, 62 | opts: Opts { 63 | block_size: Some(123), 64 | timeout: Some(3), 65 | transfer_size: Some(5556) 66 | } 67 | } 68 | )); 69 | 70 | assert_eq!( 71 | packet_to_bytes(&packet.unwrap()), 72 | b"\x00\x01abc\0netascii\0blksize\0123\0timeout\03\0tsize\05556\0"[..] 73 | ); 74 | 75 | let packet = Packet::decode( 76 | b"\x00\x01abc\0netascii\0blksize\0123\0timeout\03\0tsize\0", 77 | ); 78 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 79 | 80 | let packet = Packet::decode(b"\x00\x01abc\0netascii\0blksizeX\0123\0"); 81 | assert!(matches!(packet, Ok(Packet::Rrq(ref req)) 82 | if req == &RwReq { 83 | filename: "abc".to_string(), 84 | mode: Mode::Netascii, 85 | opts: Opts::default() 86 | } 87 | )); 88 | } 89 | 90 | #[test] 91 | fn check_wrq() { 92 | let packet = Packet::decode(b"\x00\x02abc\0octet\0"); 93 | 94 | assert!(matches!(packet, Ok(Packet::Wrq(ref req)) 95 | if req == &RwReq { 96 | filename: "abc".to_string(), 97 | mode: Mode::Octet, 98 | opts: Opts::default() 99 | } 100 | )); 101 | 102 | assert_eq!(packet_to_bytes(&packet.unwrap()), b"\x00\x02abc\0octet\0"[..]); 103 | 104 | let packet = Packet::decode(b"\x00\x02abc\0OCTet\0"); 105 | 106 | assert!(matches!(packet, Ok(Packet::Wrq(ref req)) 107 | if req == &RwReq { 108 | filename: "abc".to_string(), 109 | mode: Mode::Octet, 110 | opts: Opts::default() 111 | } 112 | )); 113 | 114 | assert_eq!(packet_to_bytes(&packet.unwrap()), b"\x00\x02abc\0octet\0"[..]); 115 | 116 | let packet = Packet::decode(b"\x00\x02abc\0octet\0more"); 117 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 118 | 119 | let packet = Packet::decode(b"\x00\x02abc\0octet"); 120 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 121 | 122 | let packet = Packet::decode(b"\x00\x02abc\0octex\0"); 123 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 124 | 125 | let packet = Packet::decode( 126 | b"\x00\x02abc\0octet\0blksize\0123\0timeout\03\0tsize\05556\0", 127 | ); 128 | 129 | assert!(matches!(packet, Ok(Packet::Wrq(ref req)) 130 | if req == &RwReq { 131 | filename: "abc".to_string(), 132 | mode: Mode::Octet, 133 | opts: Opts { 134 | block_size: Some(123), 135 | timeout: Some(3), 136 | transfer_size: Some(5556) 137 | } 138 | } 139 | )); 140 | 141 | assert_eq!( 142 | packet_to_bytes(&packet.unwrap()), 143 | b"\x00\x02abc\0octet\0blksize\0123\0timeout\03\0tsize\05556\0"[..] 144 | ); 145 | 146 | let packet = Packet::decode(b"\x00\x02abc\0octet\0blksizeX\0123\0"); 147 | assert!(matches!(packet, Ok(Packet::Wrq(ref req)) 148 | if req == &RwReq { 149 | filename: "abc".to_string(), 150 | mode: Mode::Octet, 151 | opts: Opts::default() 152 | } 153 | )); 154 | } 155 | 156 | #[test] 157 | fn check_data() { 158 | let packet = Packet::decode(b"\x00\x03\x00\x09abcde"); 159 | assert!( 160 | matches!(packet, Ok(Packet::Data(9, ref data)) if &data[..] == b"abcde") 161 | ); 162 | 163 | assert_eq!(packet_to_bytes(&packet.unwrap()), b"\x00\x03\x00\x09abcde"[..]); 164 | 165 | let packet = Packet::decode(b"\x00\x03\x00\x09"); 166 | assert!(matches!(packet, Ok(Packet::Data(9, ref data)) if data.is_empty())); 167 | assert_eq!(packet_to_bytes(&packet.unwrap()), b"\x00\x03\x00\x09"[..]); 168 | } 169 | 170 | #[test] 171 | fn check_ack() { 172 | let packet = Packet::decode(b"\x00\x04\x00\x09"); 173 | assert!(matches!(packet, Ok(Packet::Ack(9)))); 174 | assert_eq!(packet_to_bytes(&packet.unwrap()), b"\x00\x04\x00\x09"[..]); 175 | 176 | let packet = Packet::decode(b"\x00\x04\x00"); 177 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 178 | 179 | let packet = Packet::decode(b"\x00\x04\x00\x09a"); 180 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 181 | } 182 | 183 | #[test] 184 | fn check_error() { 185 | let packet = Packet::decode(b"\x00\x05\x00\x01msg\0"); 186 | assert!(matches!(packet, Ok(Packet::Error(packet::Error::FileNotFound)))); 187 | assert_eq!( 188 | packet_to_bytes(&packet.unwrap()), 189 | b"\x00\x05\x00\x01File not found\0"[..] 190 | ); 191 | 192 | // 0x10 is unknown error code an will be treated as 0 193 | let packet = Packet::decode(b"\x00\x05\x00\x10msg\0"); 194 | assert!(matches!(packet, Ok(Packet::Error(packet::Error::Msg(ref errmsg))) 195 | if errmsg == "msg")); 196 | assert_eq!(packet_to_bytes(&packet.unwrap()), b"\x00\x05\x00\x00msg\0"[..]); 197 | 198 | let packet = Packet::decode(b"\x00\x05\x00\x00msg\0"); 199 | assert!(matches!(packet, Ok(Packet::Error(packet::Error::Msg(ref errmsg))) 200 | if errmsg == "msg")); 201 | assert_eq!(packet_to_bytes(&packet.unwrap()), b"\x00\x05\x00\x00msg\0"[..]); 202 | 203 | let packet = Packet::decode(b"\x00\x05\x00\x00msg\0more"); 204 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 205 | 206 | let packet = Packet::decode(b"\x00\x05\x00\x00msg"); 207 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 208 | 209 | let packet = Packet::decode(b"\x00\x05\x00\x00"); 210 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 211 | } 212 | 213 | #[test] 214 | fn check_oack() { 215 | let packet = Packet::decode(b"\x00\x06"); 216 | assert!( 217 | matches!(packet, Ok(Packet::OAck(ref opts)) if opts == &Opts::default()) 218 | ); 219 | 220 | let packet = Packet::decode(b"\x00\x06blksize\0123\0"); 221 | assert!(matches!(packet, Ok(Packet::OAck(ref opts)) 222 | if opts == &Opts { 223 | block_size: Some(123), 224 | timeout: None, 225 | transfer_size: None 226 | } 227 | )); 228 | 229 | let packet = Packet::decode(b"\x00\x06timeout\03\0"); 230 | assert!(matches!(packet, Ok(Packet::OAck(ref opts)) 231 | if opts == &Opts { 232 | block_size: None, 233 | timeout: Some(3), 234 | transfer_size: None 235 | } 236 | )); 237 | 238 | let packet = Packet::decode(b"\x00\x06tsize\05556\0"); 239 | assert!(matches!(packet, Ok(Packet::OAck(ref opts)) 240 | if opts == &Opts { 241 | block_size: None, 242 | timeout: None, 243 | transfer_size: Some(5556), 244 | } 245 | )); 246 | 247 | let packet = 248 | Packet::decode(b"\x00\x06tsize\05556\0blksize\0123\0timeout\03\0"); 249 | assert!(matches!(packet, Ok(Packet::OAck(ref opts)) 250 | if opts == &Opts { 251 | block_size: Some(123), 252 | timeout: Some(3), 253 | transfer_size: Some(5556), 254 | } 255 | )); 256 | } 257 | 258 | #[test] 259 | fn check_packet() { 260 | let packet = Packet::decode(b"\x00\x07"); 261 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 262 | 263 | let packet = Packet::decode(b"\x00\x05\x00"); 264 | assert!(matches!(packet, Err(ref e) if matches!(e, Error::InvalidPacket))); 265 | } 266 | 267 | #[test] 268 | fn check_blksize_boundaries() { 269 | let opts = parse_opts(b"blksize\07\0").unwrap(); 270 | assert_eq!( 271 | opts, 272 | Opts { 273 | block_size: None, 274 | ..Opts::default() 275 | } 276 | ); 277 | 278 | let opts = parse_opts(b"blksize\08\0").unwrap(); 279 | assert_eq!( 280 | opts, 281 | Opts { 282 | block_size: Some(8), 283 | ..Opts::default() 284 | } 285 | ); 286 | 287 | let opts = parse_opts(b"blksize\065464\0").unwrap(); 288 | assert_eq!( 289 | opts, 290 | Opts { 291 | block_size: Some(65464), 292 | ..Opts::default() 293 | } 294 | ); 295 | 296 | let opts = parse_opts(b"blksize\065465\0").unwrap(); 297 | assert_eq!( 298 | opts, 299 | Opts { 300 | block_size: None, 301 | ..Opts::default() 302 | } 303 | ); 304 | } 305 | 306 | #[test] 307 | fn check_timeout_boundaries() { 308 | let opts = parse_opts(b"timeout\00\0").unwrap(); 309 | assert_eq!( 310 | opts, 311 | Opts { 312 | timeout: None, 313 | ..Opts::default() 314 | } 315 | ); 316 | 317 | let opts = parse_opts(b"timeout\01\0").unwrap(); 318 | assert_eq!( 319 | opts, 320 | Opts { 321 | timeout: Some(1), 322 | ..Opts::default() 323 | } 324 | ); 325 | 326 | let opts = parse_opts(b"timeout\0255\0").unwrap(); 327 | assert_eq!( 328 | opts, 329 | Opts { 330 | timeout: Some(255), 331 | ..Opts::default() 332 | } 333 | ); 334 | 335 | let opts = parse_opts(b"timeout\0256\0").unwrap(); 336 | assert_eq!( 337 | opts, 338 | Opts { 339 | timeout: None, 340 | ..Opts::default() 341 | } 342 | ); 343 | } 344 | -------------------------------------------------------------------------------- /src/tests/random_file.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "external-client-tests")] 2 | #![cfg(target_os = "linux")] 3 | 4 | use async_channel::Sender; 5 | use futures_lite::AsyncRead; 6 | use rand::rngs::SmallRng; 7 | use rand::{RngCore, SeedableRng}; 8 | use std::cmp; 9 | use std::io::{self, Read}; 10 | use std::pin::Pin; 11 | use std::task::{Context, Poll}; 12 | 13 | pub struct RandomFile { 14 | size: usize, 15 | read_size: usize, 16 | rng: SmallRng, 17 | md5_ctx: Option, 18 | md5_tx: Option>, 19 | } 20 | 21 | impl RandomFile { 22 | pub fn new(size: usize, md5_tx: Sender) -> Self { 23 | RandomFile { 24 | size, 25 | read_size: 0, 26 | rng: SmallRng::from_entropy(), 27 | md5_ctx: Some(md5::Context::new()), 28 | md5_tx: Some(md5_tx), 29 | } 30 | } 31 | } 32 | 33 | impl Read for RandomFile { 34 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 35 | if self.size == self.read_size { 36 | if let (Some(md5_ctx), Some(md5_tx)) = 37 | (self.md5_ctx.take(), self.md5_tx.take()) 38 | { 39 | md5_tx 40 | .try_send(md5_ctx.compute()) 41 | .expect("failed to send md5 digest"); 42 | } 43 | 44 | Ok(0) 45 | } else { 46 | let len = cmp::min(buf.len(), self.size - self.read_size); 47 | 48 | self.rng.fill_bytes(&mut buf[..len]); 49 | self.md5_ctx.as_mut().unwrap().consume(&buf[..len]); 50 | self.read_size += len; 51 | 52 | Ok(len) 53 | } 54 | } 55 | } 56 | 57 | impl AsyncRead for RandomFile { 58 | fn poll_read( 59 | mut self: Pin<&mut Self>, 60 | _cx: &mut Context, 61 | buf: &mut [u8], 62 | ) -> Poll> { 63 | Poll::Ready(self.read(buf)) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/tests/rrq.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "external-client-tests")] 2 | #![cfg(target_os = "linux")] 3 | 4 | use async_executor::Executor; 5 | use blocking::Unblock; 6 | use futures_lite::future::block_on; 7 | use std::cell::Cell; 8 | use std::rc::Rc; 9 | use std::sync::Arc; 10 | 11 | use super::external_client::*; 12 | use super::handlers::*; 13 | use crate::server::TftpServerBuilder; 14 | 15 | fn transfer(file_size: usize, block_size: Option) { 16 | let ex = Arc::new(Executor::new()); 17 | let transfered = Rc::new(Cell::new(false)); 18 | 19 | block_on(ex.run({ 20 | let ex = ex.clone(); 21 | let transfered = transfered.clone(); 22 | 23 | async move { 24 | let (md5_tx, md5_rx) = async_channel::bounded(1); 25 | let handler = RandomHandler::new(file_size, md5_tx); 26 | 27 | // bind 28 | let tftpd = TftpServerBuilder::with_handler(handler) 29 | .bind("127.0.0.1:0".parse().unwrap()) 30 | .build() 31 | .await 32 | .unwrap(); 33 | let addr = tftpd.listen_addr().unwrap(); 34 | 35 | // start client 36 | let mut tftp_recv = Unblock::new(()); 37 | let tftp_recv = tftp_recv.with_mut(move |_| { 38 | external_tftp_recv("test", addr, block_size) 39 | }); 40 | 41 | // start server 42 | ex.spawn(async move { 43 | tftpd.serve().await.unwrap(); 44 | }) 45 | .detach(); 46 | 47 | // check md5 48 | let client_md5 = 49 | tftp_recv.await.expect("failed to run tftp client"); 50 | let server_md5 = 51 | md5_rx.recv().await.expect("failed to receive server md5"); 52 | assert_eq!(client_md5, server_md5); 53 | 54 | transfered.set(true); 55 | } 56 | })); 57 | 58 | assert!(transfered.get()); 59 | } 60 | 61 | #[test] 62 | fn transfer_0_bytes() { 63 | transfer(0, None); 64 | transfer(0, Some(1024)); 65 | } 66 | 67 | #[test] 68 | fn transfer_less_than_block() { 69 | transfer(1, None); 70 | transfer(123, None); 71 | transfer(511, None); 72 | transfer(1023, Some(1024)); 73 | } 74 | 75 | #[test] 76 | fn transfer_block() { 77 | transfer(512, None); 78 | transfer(1024, Some(1024)); 79 | } 80 | 81 | #[test] 82 | fn transfer_more_than_block() { 83 | transfer(512 + 1, None); 84 | transfer(512 + 123, None); 85 | transfer(512 + 511, None); 86 | transfer(1024 + 1, Some(1024)); 87 | transfer(1024 + 123, Some(1024)); 88 | transfer(1024 + 1023, Some(1024)); 89 | } 90 | 91 | #[test] 92 | fn transfer_1mb() { 93 | transfer(1024 * 1024, None); 94 | transfer(1024 * 1024, Some(1024)); 95 | } 96 | 97 | #[test] 98 | #[ignore] 99 | fn transfer_almost_32mb() { 100 | transfer(32 * 1024 * 1024 - 1, None); 101 | } 102 | 103 | #[test] 104 | #[ignore] 105 | fn transfer_32mb() { 106 | transfer(32 * 1024 * 1024, None); 107 | } 108 | 109 | #[test] 110 | #[ignore] 111 | fn transfer_more_than_32mb() { 112 | transfer(33 * 1024 * 1024 + 123, None); 113 | } 114 | 115 | #[test] 116 | #[ignore] 117 | fn transfer_more_than_64mb() { 118 | transfer(65 * 1024 * 1024 + 123, None); 119 | } 120 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use async_io::Timer; 2 | use futures_lite::future; 3 | use std::future::Future; 4 | use std::io; 5 | use std::time::Duration; 6 | 7 | pub async fn io_timeout( 8 | dur: Duration, 9 | f: impl Future>, 10 | ) -> io::Result { 11 | future::race(f, async move { 12 | Timer::after(dur).await; 13 | Err(io::ErrorKind::TimedOut.into()) 14 | }) 15 | .await 16 | } 17 | --------------------------------------------------------------------------------