Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

A simple TFTP client using C#

4.71/5 (16 votes)
2 Jan 2013CPOL6 min read 1   5.9K  
An article about creating a TFTP client with C#.

Introduction

There are lots of small network devices using TFTP for transferring configurations or firmware. This project was created while trying to GET and PUT some configuration files to a network switch. Because there where only commercial libraries in the web, I decided to publish my sample implementation. Since TFTP uses UDP, it is quite easy to create a simple client. The following paragraph gives a small introduction into the TFTP protocol, which is described in detail in RFC 1350.

Some words on TFTP

TFTP only supports two methods: READ REQUEST (RRQ) and WRITE REQUEST (WRQ). There is neither native support of directories nor any function to list all files. The user has to know what file to be read or write.

The protocol

The first packet sent to the server is always a request (RRQ/WRQ), followed by data as well as acknowledgement packages. If something goes wrong, an error packet will be sent.

Read Session
ClientServer
RRQ packet (filename and mode) to port 69. Source port becomes CTID.
DATA packet (block number) to port: CTID. Source port becomes STID.
ACK packet (block number) to port: STID
Next DATA packet ...

A write request will be handled the same way. Instead of the first data packet, the server will respond to the request with an ACK packet where the block number is zero.

The packets

To identify the different packet types, TFTP defines five Opcodes. Depending on the Opcode, the packet may have a different structure.

Overview of Opcodes
OpcodeOperation
1Read request (RRQ)
2Write request (WRQ)
3Data (DATA)
4Acknowledgment (ACK)
5Error (ERROR)

RRQ/WRQ request

Each session will start with a request (read / write) packet from the client which will be sent directly to the servers port (e.g., 69). The server will either answer with the first data packet (RRQ) or an acknowledgement packet (WRQ). The source port of the client packet is the client side TID, and the source port of the server side is the server's TID (transfer ID). The next packet from the client will be sent to the server using the server's TID as the destination port and vice versa. The TIDs are constant while the transfer is active.

Each request packet will contain the Opcode, the filename terminated by a zero, and the transfer mode terminated by a zero.

Request Packet
2 bytesString1 byteString1 byte
OpcodeFilename0Mode0

Depending on the type of request, a data packet for RRQ or an acknowledgement packet for WRQ will follow. If something goes wrong, the server will send an error packet.

DATA and ACK packets

UDP does not provide a streaming functionality by itself. Therefore, the combination of the server and the client TIDs will be used as a "virtual channel". TFTP will use a block number for each data packet, which has to be acknowledged. If a packet is not acknowledged in time (some seconds), the sender will repeat the data packet automatically until it is acknowledged. Acknowledgement packets will be answered with the next data packet. Because each data packet should be 512 (data) bytes long, the last packet will have between 0 and 511 data bytes.

Data Packet
2 bytes2 bytesData bytes
OpcodeBlock numberData

The acknowledgemet packets have a length of 4 bytes. They only consist of the Opcode and the block number to acknowledge.

ACK Packet
2 bytes2 bytes
OpodeBlock number

In some cases, the server might send an error packet which consists of the Opcode, the error code, and a message terminated by one or more zeros. Many TFTP servers will stuff the error message with "\0" to equalize the length of all packet types.

Error Packet
2 bytes2 bytesString1 byte
OpcodeError codeError message0

Implementation

The TFTP client is small enough to fit into one class. For convenience, I decided to define enumerations for the Opcodes, modes, and a special exception class for the TFTP failures. Please keep in mind that this is just a simple example implementation which will work well for the author, but it is not yet complete (see the To Do list).

The TFTPClient has two different ctors, one with the server name and another with the server name and port. Usually, there will be no need to change the server's port.

C#
/// <summary>
/// Initializes a new instance of the <see cref="TFTPClient"/> class.
/// </summary>
/// <param name="server">The server.</param>
public TFTPClient(string server)
   : this(server, 69) {
}

/// <summary>
/// Initializes a new instance of the <see cref="TFTPClient"/> class.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="port">The port.</param>
public TFTPClient(string server, int port) {
  Server = server;
  Port = port;
}

The Get function is the first public function of the TFTPClient. The function is designed to work thread-safe. Because of this, it needs to create its own new IPEndPoints and Sockets per request.

C#
public void Get(string remoteFile, string localFile, Modes tftpMode) {
  int len = 0;
  int packetNr = 1;
  byte[] sndBuffer = CreateRequestPacket(Opcodes.Read, remoteFile, tftpMode);
  byte[] rcvBuffer = new byte[516];

  BinaryWriter fileStream = new BinaryWriter(new FileStream(localFile,
      FileMode.Create, FileAccess.Write, FileShare.Read));
  IPHostEntry hostEntry = Dns.GetHostEntry(tftpServer);
  IPEndPoint serverEP = new IPEndPoint(hostEntry.AddressList[0], tftpPort);
  EndPoint dataEP = (EndPoint)serverEP;
  Socket tftpSocket = new Socket(serverEP.Address.AddressFamily,
                                 SocketType.Dgram, ProtocolType.Udp);

In the next step, the request packet will be created by a specialized function and sent on the socket. After retrieving the answer, it is needs to change the destination port of our EndPoint because the STID is to be used. If a packet gets lost, the server will repeat the packet  until it is acknowledged  This implementation will not wait for the repetition; instead, it will run into a timeout and throw a SocketException! In this case, the user has to retry.

C#
// Request and Receive first Data Packet From TFTP Server
tftpSocket.SendTo(sndBuffer, sndBuffer.Length, SocketFlags.None, serverEP);
tftpSocket.ReceiveTimeout = 1000 ;
len = tftpSocket.ReceiveFrom(rcvBuffer, ref dataEP);

// keep track of the TID
serverEP.Port = ((IPEndPoint)dataEP).Port;

Now, loop until the received data packet is less than 516 bytes (including the header) long. Check the Block number, it might be a repeated packet. Write the data to the destination file and send the ACK packet using a specialized packet creator function.

C#
 while (true) {
   // handle any kind of error 
  if (((Opcodes)rcvBuffer[1]) == Opcodes.Error) {
    fileStream.Close();
    tftpSocket.Close();
    throw new TFTPException(
      ((rcvBuffer[2] << 8) & 0xff00) | rcvBuffer[3],
       Encoding.ASCII.GetString(rcvBuffer, 4, rcvBuffer.Length - 5).Trim('\0'));
  }
  // expect the next packet
  if ((((rcvBuffer[2] << 8) & 0xff00) | rcvBuffer[3]) == packetNr) {
     // Store to local file
     fileStream.Write(rcvBuffer, 4, len - 4);

     // Send Ack Packet to TFTP Server
     sndBuffer = CreateAckPacket(packetNr++);
     tftpSocket.SendTo(sndBuffer, sndBuffer.Length, SocketFlags.None, serverEP);
  }

  // Was it the last packet ?
  if (len < 516) {
    break;
  } else {
    // Receive Next Data Packet From TFTP Server
    len = tftpSocket.ReceiveFrom(rcvBuffer, ref dataEP);
  }
}

If the file is transferred, close all handles and exit the function.

C#
  // Close Socket and release resources
  tftpSocket.Close();
  fileStream.Close();
}

The same procedure applies for the Put function. The one and only difference is that we have to send the data and wait for ACK packets. For more details, please have a look at the provided source file.

Please note that this implementation will be able to handle lost data or ACK packets from the server or the client. From my point of view, it is not necessarily required to do so because TFTP is mostly used on a short distance in a LAN. So, there should be no packet loss.

Usage

The usage is as easy as pie. Just create a instance of the TFTPClient class and use it for reading and writing from and to the server. Keep in mind that there is currently no re-encoding so you should use the transfer mode octet, which is the default.

C#
TFTPClient t = new TFTPClient("127.0.0.1");
t.Put(@"test.zip", @"c:\Temp\MyDemoFileWrite.zip");
t.Get(@"test.zip", @"c:\temp\MyDemoFileRead.zip");

Points of interest

Well, none really. It's just a straightforward solution to solve a coding problem.

To Do

  • The client should repeat each unanswered message  until it is acknowledged after a timeout.
  • Handle the different encodings (netasci, octet) - (not sure if it is really needed).

History

  • 2007.06.22 - First cut.
  • 2007.06.24 - Link to source file fixed.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)