Introduction

Trivial File Transfer Protocol (TFTP) is a simple lockstep File Transfer Protocol which allows a client to get a file from or put a file onto a remote host.

TFTP is designed to be small and easy to implement. Therefore, It's a nice protocol to study about networks.

Because of simplicity, TFTP use very small memory footprint. Ideal for embedded systems.

Today, TFTP is virtually unused for Internet transfers, Generally only used on local area networks (LAN)

It is implemented on top of the UDP/IP protocols using well-known port number 69.

Request

The client first requests the server to read or write some data. At well known port 69.

Request frame containing the filename, transfer mode and optionally any negotiated option. (We will not discuss those options in this tutorial)

#![allow(unused)]
fn main() {
pub struct Resource {
    pub filename: Text,
    pub mode: Text,
}
}

Text is nul-terminated modified version of ascii string.

Data Transfer

Based on request, The server or client sends the data frame to the other endpoint. Data frame contains a block number and payload.

The payload is sent in a fixed length buffer of 512 bytes by default or the number specified in the block size negotiated option.

The last data block must be less than the negotiated or default blocksize (which is 512) to signal the end of the transfer.

What happens when the last block is exact block sized ?

If that happens, the endpoint sends a data frame of 0 byte to signal the end of the transfer.

Initially, block number is 1. On each transfer, the block number is incremented by one.

What happens when the block number is exhausted ?

According to wikipedia, The original protocol has a transfer file size limit of 512 * 65535 blocks = 32 MB, Today most servers and clients support block number roll-over (block counter going back to 0 or 1 after 65535) which gives an essentially unlimited transfer file size.

If the response is positive, the server create a new socket and all transfers are performed using this new socket.

This significantly simplifies the implementation of overall protocol. As we don't need to track each socket.

Acknowledgement

For various reasons, there might be some packet lost. The sender detect packet loss using a timer and retransmit missing packets. This is known as acknowledgement.

Acknowledgement indicate that the data has been received.

An Endpoint must send an acknowledgement packet for each data packet received. Acknowledgement number must be the same as the block number of the data packet.

RRQ
WRQ

It guarantees that all old packets are received and prevents network congestion.

Positive response to a write request is an acknowledgment packet, in this special case the block number will be zero.

Error

Any errors cause termination of the connection. This packet is not acknowledged, and not retransmitted.

#![allow(unused)]
fn main() {
#[repr(u16)]
pub enum ErrorCode {
    NotDefined,
    FileNotFound,
    AccessViolation,
    DiskFull,
    IllegalOperation,
    UnknownTransferID,
    FileAlreadyExists,
    NoSuchUser,
}
}

An error frame contains an error code and reason.

#![allow(unused)]
fn main() {
pub struct Error {
    pub code: ErrorCode,
    pub reason: Text,
}
}

Frame

In networking, a frame is a fundamental unit of data transmission between two endpoint.

#![allow(unused)]
fn main() {
#[repr(u16)]
pub enum Frame {
    Read(Resource) = 1,
    Write(Resource) = 2,
    Data { block: u16, bytes: Bytes } = 3,
    Acknowledge(u16) = 4,
    Error(Error) = 5,
}
}

Here is the binary format of each packet:

Data Format

The first 2 bytes of each packet type are known as the opcode or discriminant, It is just a number that identifies the type of the frame.

OpcodeFrame Type
1Read Request
2Write Request
3Data
4Acknowledgment
5Error

User Datagram Protocol

TFTP uses UDP to transfer it's data.

Protocol Stack
Local Medium
Internet Protocol
UDP
TFTP Frame

The UDP header consists of the following fields.

FieldType
Source Portu16
Destination Portu16
Lengthu16
Checksumu16
  • Port number: Port numbers as a way to identify a service. Suppose you want to send a mail to your friend, his hotel address is "Main Street, Anytown, USA" and room number is "123", You can think street address as a IP address and room number as a Port number.

  • Length: Length field defines the length of the message in bytes. Maximum value of u16 bit integer is 65535. However, the actual length of the message is 65,507 bytes* (65,535 − 8 bytes UDP header − 20 bytes IP header)

  • Checksum: The checksum field may be used for error-checking of the header and data. This field is optional in IPv4, and mandatory in IPv6.

Reference