Click here to Skip to main content
Licence CPOL
First Posted 23 Jun 2007
Views 52,208
Downloads 1,142
Bookmarked 33 times

A simple TFTP client using C#

By | 23 Jun 2007 | Article
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 five times. 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 up to five times. 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 up to five times 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)

About the Author

Matthias.Fischer

Software Developer (Senior)
Tieto Deutschland GmbH
Germany Germany

Member

Follow on Twitter Follow on Twitter


Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralThank You Pinmembersarayu43323:46 23 May '12  
AnswerSolution for 'An existing connection was forcibly closed by the remote host' exception. Pinmemberdim1316:59 24 Jun '11  
GeneralRe: Solution for 'An existing connection was forcibly closed by the remote host' exception. Pinmemberjuidan10:47 19 Dec '11  
QuestionException under Windows 7 PinmemberCharleElks14:29 6 Dec '10  
GeneralVery nice and clean and easy to use code Pinmemberhjgode4:42 9 Jun '10  
GeneralServer counterpart Pinmemberjpmik8:42 23 Apr '10  
QuestionHow to use PinmemberMongkol_MSNE5:30 5 Sep '09  
Questionmissing last ACK in Put method ? Pinmembersnupi3:35 9 Apr '09  
QuestionHow to speed up? Pinmemberripper332:04 25 Mar '09  
AnswerRe: How to speed up? Pinmemberripper332:50 25 Mar '09  
GeneralAdd retry PinmemberMember 38088754:31 15 Jan '09  
GeneralFile Not Found Handeling Pinmemberpoppopreturn10:37 10 Nov '08  
Generalget server file length Pinmemberchris pang17:43 14 Aug '08  
GeneralpacketNr must be 16Bit Pinmemberfreakshow23:24 30 Jul '08  
QuestionCan i get this source that made by C++?? PinmemberSong sae woom22:28 1 Jun '08  
AnswerRe: Can i get this source that made by C++?? PinmemberMatthias.Fischer21:32 11 Jun '08  
GeneralCan't get it to "put" successfully Pinmemberdrspence0074:13 25 May '08  
AnswerRe: Can't get it to "put" successfully PinmemberMatthias.Fischer21:30 11 Jun '08  
GeneralSome problems PinmemberSergey.Boldyrev5:29 23 Mar '08  
AnswerRe: Some problems PinmemberMatthias.Fischer22:28 26 Mar '08  
Generaldownload does not work Pinmemberoldpasch8:36 5 Dec '07  
GeneralRe: download does not work PinmemberMatthias.Fischer22:53 7 Feb '08  
Generaldownload does not work Pinmemberoldpasch8:34 5 Dec '07  
Generalbug report Pinmemberdavi605:20 8 Oct '07  
AnswerRe: bug report PinmemberMatthias.Fischer22:17 28 Feb '08  

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.

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120517.1 | Last Updated 23 Jun 2007
Article Copyright 2007 by Matthias.Fischer
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid