Click here to Skip to main content
Click here to Skip to main content

A simple TFTP client using C#

, 2 Jan 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
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
Client Server
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
Opcode Operation
1 Read request (RRQ)
2 Write request (WRQ)
3 Data (DATA)
4 Acknowledgment (ACK)
5 Error (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 bytes String 1 byte String 1 byte
Opcode Filename 0 Mode 0

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 bytes 2 bytes Data bytes
Opcode Block number Data

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

ACK Packet
2 bytes 2 bytes
Opode Block 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 bytes 2 bytes String 1 byte
Opcode Error code Error message 0

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.

  /// <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.

 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.

 // 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.

 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.

  // 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.

   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)

Share

About the Author

Matthias.Fischer
Software Developer (Senior) MF IT Consult
Germany Germany
My name is Matthias Fischer. I’m Nokia Developer Certified Trainer, a .NET consultant and a textbook writer living in Rathenow, Germany. For nearly 10 years I’m co-organizer of the .NET user group in Berlin-Brandenburg. I’m an experienced software developer, architect and speaker for mobile and web solutions since 2001. In the last years I have focused the following technologies and frameworks: Windows Phone, WIndows 8, WPF, WCF, ASP.NET, ASP.NET MVC, ADO.NET, jQuery
Follow on   Twitter   Google+

Comments and Discussions

 
BugCan't Get file from FilePath including specific Drive Info. PinprofessionalMember 1042211926-Nov-13 18:55 
QuestionFile is in use exception PinmemberVDohnal7-Oct-12 22:36 
Question"hostEntry" exception Pinmemberelt42at16-Sep-12 23:46 
QuestionOther forms? PinmemberMember 908191320-Jun-12 4:40 
GeneralThanks A Lot PinmemberSyst3r16-Jun-12 15:04 
GeneralThank You Pinmembersarayu43324-May-12 0:46 
AnswerSolution for 'An existing connection was forcibly closed by the remote host' exception. Pinmemberdim1324-Jun-11 17:59 
GeneralRe: Solution for 'An existing connection was forcibly closed by the remote host' exception. Pinmemberjuidan19-Dec-11 11:47 
GeneralRe: Solution for 'An existing connection was forcibly closed by the remote host' exception. PinmemberGroovyDave6-Jun-12 15:22 
QuestionException under Windows 7 PinmemberCharleElks6-Dec-10 15:29 
AnswerRe: Exception under Windows 7 PinmemberBeppi23-Jul-14 1:25 
GeneralVery nice and clean and easy to use code Pinmemberhjgode9-Jun-10 5:42 
GeneralServer counterpart Pinmemberjpmik23-Apr-10 9:42 
GeneralRe: Server counterpart PinmemberVDohnal8-Oct-12 2:49 
GeneralRe: Server counterpart Pinmemberjpmik9-Oct-12 7:07 
GeneralRe: Server counterpart Pinmembergiadich9-Jan-13 18:28 
QuestionHow to use PinmemberMongkol_MSNE5-Sep-09 6:30 
Questionmissing last ACK in Put method ? Pinmembersnupi9-Apr-09 4:35 
AnswerRe: missing last ACK in Put method ? PinmemberVDohnal8-Oct-12 0:49 
QuestionHow to speed up? Pinmemberripper3325-Mar-09 3:04 
AnswerRe: How to speed up? Pinmemberripper3325-Mar-09 3:50 
AnswerRe: How to speed up? Pinmembergiadich9-Jan-13 18:34 
GeneralAdd retry PinmemberMember 380887515-Jan-09 5:31 
GeneralRe: Add retry Pinmembergiadich9-Jan-13 18:09 
GeneralFile Not Found Handeling Pinmemberpoppopreturn10-Nov-08 11:37 
GeneralpacketNr must be 16Bit Pinmemberfreakshow31-Jul-08 0:24 
GeneralRe: packetNr must be 16Bit Pinmembergiadich9-Jan-13 18:04 
QuestionCan i get this source that made by C++?? PinmemberSong sae woom1-Jun-08 23:28 
AnswerRe: Can i get this source that made by C++?? PinmemberMatthias.Fischer11-Jun-08 22:32 
GeneralCan't get it to "put" successfully Pinmemberdrspence00725-May-08 5:13 
AnswerRe: Can't get it to "put" successfully PinmemberMatthias.Fischer11-Jun-08 22:30 
GeneralSome problems PinmemberSergey.Boldyrev23-Mar-08 6:29 
AnswerRe: Some problems PinmemberMatthias.Fischer26-Mar-08 23:28 
Generaldownload does not work Pinmemberoldpasch5-Dec-07 9:36 
GeneralRe: download does not work PinmemberMatthias.Fischer7-Feb-08 23:53 
Generaldownload does not work Pinmemberoldpasch5-Dec-07 9:34 
Generalbug report Pinmemberdavi608-Oct-07 6:20 
AnswerRe: bug report PinmemberMatthias.Fischer28-Feb-08 23:17 
GeneralRe: bug report Pinmembergiadich9-Jan-13 18:22 
GeneralSource code missing PinmemberTony Bermudez23-Jun-07 10:26 
AnswerRe: Source code missing Pinmembermatthias.fischer23-Jun-07 23:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.1411019.1 | Last Updated 2 Jan 2013
Article Copyright 2007 by Matthias.Fischer
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid