TFTP

Trivial File Transfer Protocol (TFTP) could be considered the Xmodem of Ethernet. TFTP is a basic lock-step protocol that supports file transfer using UDP. MicroMonitor supports both the TFTP client and the TFTP server. You can use the server to transfer files in either direction. The client can only retrieve files from a remote server. Like Xmodem, TFTP is too big a topic to cover completely in this book. However, I will touch on some of the MicroMonitor functions that support TFTP.

processTFTP()

The processTFTP() function (see Listing 10.4) is called out of processPACKET() (see Listing 9.5). For TFTP transfers, processTFTP() must handle five different standard requests (or opcodes):

  • TFTP_RRQ Read request. A remote system is requesting that a file or data be transferred from this target to the remote system.

  • TFTP_WRQ Write request. A remote system is requesting that a file or data be transferred to this target from the remote system.

  • TFTP_DAT A packet of data is incoming, as a result of acknowledging a previous WRQ or DAT.

  • TFTP_ACK A packet of data has been received at the remote system, meaning that the next packet can be sent.

  • TFTP_ERR Some type of error occurred.

The switch statement at the bottom of Listing 10.4 dispatches the processing of each of these opcodes.

Listing 10.4: processTFTP() .
image from book
 int processTFTP(struct ether_header *ehdr,ushort size) {     static  uchar   *oaddr;     struct  ip *ihdr;     struct  Udphdr *uhdr;     uchar   *data;     int     count, tmpcount;     ushort  opcode, block, errcode;     char    *comma, *tftpp, *filename, *mode, *errstring, msg[64];     if (TftpTurnedOff) {         SendICMPUnreachable(ehdr,ICMP_UNREACHABLE_PORT);         return(0);     }     ihdr = (struct ip *)(ehdr + 1);     uhdr = (struct Udphdr *)((char *)ihdr + IP_HLEN(ihdr));     tftpp = (char *)(uhdr + 1);     opcode = *(ushort *)tftpp;     switch (opcode) { 
image from book
 

Notice that processTFTP() (see Listing 10.4) takes arguments similar to the processPacket() arguments. The processTFTP() function does a lot of structure overlays onto the incoming packet. If the TFTP facility is disabled for some reason, the TftpTurnedOff flag is set. When this flag is set, the target responds to incoming packets with an ICMP Unreachable Port message.

Listing 10.5: Initiating a Download.
image from book
 case htons(TFTP_WRQ):         filename = tftpp+2;         if ((EtherVerbose & SHOW_TFTP)  (!MFLAGS_NOTFTPPRN()))             printf("TFTP rcvd WRQ: file %s\n", filename);         if (!tftpStartSrvrFilter(ehdr,uhdr))             return(0);         mode = filename;         while(*mode)             mode++;         mode++;                  /* Destination of WRQ can be an address (0x...), environment          * variable ($...) or a TFS filename...          */         if ((filename[0] == '$') && (getenv(&filename[1]))) {             TftpAddr = (uchar *)strtol(getenv(&filename[1]),(char **)0,0);         }         else if ((filename[0] == '0') && (filename[1] == 'x')) {             TftpAddr = (uchar *)strtol(filename,(char **)0,0);         }         else {             if (MFLAGS_NOTFTPOVW() && tfsstat(filename)) {                 SendTFTPErr(ehdr,6,"File already exists.",1);                 return(0);             }             TftpAddr = (uchar *)getAppRamStart();             strncpy(TftpTfsFname,filename,sizeof(TftpTfsFname)-1);             TftpTfsFname[sizeof(TftpTfsFname)-1] = 0;         }         TftpCount = -1; /* not used with WRQ, so clear it */         /* Convert mode to lower case... */         strtolower(mode);         if (!strcmp(mode,"netascii"))             TftpWrqMode = MODE_NETASCII;         else if (!strcmp(mode,"octet"))             TftpWrqMode = MODE_OCTET;         else {             SendTFTPErr(ehdr,0,"Mode not supported.",1);             TftpWrqMode = MODE_NULL;             TftpCount = -1;             return(0);         }         block = 0;         tftpLastblock = block;         oaddr = TftpAddr;         TftpChopCount = 0;         break; 
image from book
 

The first opcode processed is TFTP_WRQ (Listing 10.5). Notice immediately that the switch tests htons ( TFTP_WRQ ), not just TFTP_WRQ . Wrapping the reference in this macro guarantees that the incoming opcode value and the opcode value on the target have the same endian-ness. The first step is to verify that the local TFTP server is in the proper state to deal with a WRQ request (the call to TftpStartSrvrFilter() , not shown). If the state is incorrect, the response is a TFTP_ERR message (in TftpStartSrvrFilter() ), and the return value is zero.

The incoming packet contains the filename and the mode by which the file is to be transferred. This implementation accepts three different types of file names and two different modes, as follows :

Filename options:

  • a standard name , in which case the data is first copied to RAM and then transferred via tfsadd() to TFS.

  • a hexadecimal address, assumed to start with 0x , in which case the data is transferred directly to the specified location.

  • an address stored in a shell variable, assumed to start with $ , in which case the data is transferred to the hexadecimal address contained in the shell variable.

Mode options:

  • netascii for transfer of ASCII files. Incoming \r\n is replaced with \n .

  • octet for transfer of binary files. Incoming data is left untouched.

These options give the user the ability to transfer directly to RAM through a hard-coded address or through the contents of a shell variable. (Yes, this statement does mean that you cannot transfer a file whose name starts with 0x or $ , but this restriction is reasonable.) Notice the macro MFLAGS_NOTFTPOVW() . This line tests whether the requested file transfer is to a file that already exists. If a flag in the system has been set that disallows the overwrite of an existing file through TFTP, the monitor returns TFTP_ERR . Thats about it for TFTP_WRQ . The server state advances to "receiving" and the request is acknowledged (with ACK ).

Listing 10.6: Initiating an Upload.
image from book
 case htons(TFTP_RRQ):         filename = tftpp+2;         if ((EtherVerbose & SHOW_TFTP)  (!MFLAGS_NOTFTPPRN()))             printf("TFTP rcvd RRQ: file %s\n",filename);         if (!tftpStartSrvrFilter(ehdr,uhdr))             return(0);         mode = filename;         while(*mode) mode++;         mode++;         comma = strchr(filename,',');         if (!comma) {             TFILE   *tfp;              tfp = tfsstat(filename);             if (!tfp) {                 SendTFTPErr(ehdr,0,"File not found, try 'address,count'",1);                 TftpCount = -1;                 return(0);             }             TftpAddr = (uchar *)TFS_BASE(tfp);             TftpCount = TFS_SIZE(tfp);         }         else {             comma++;             TftpAddr = (uchar *)strtol(filename,(char **)0,0);             TftpCount = strtol(comma,(char **)0,0);         }         if (strcmp(mode,"octet")) {             SendTFTPErr(ehdr,0,"Must use binary mode",1);             TftpCount = -1;             return(0);         }         block = tftpLastblock = 1;         tftpGotoState(TFTPACTIVE);         SendTFTPData(ehdr,block,TftpAddr,TftpCount);         return(0); 
image from book
 

Listing 10.6 shows the code that processes TFTP_RRQ requests. The code once again begins with some state verification (the call to tftpStartSrvrFilter() .) The format for an incoming packet is similar to that recognized by TFTP_WRQ . This implementation accepts two different filename syntaxes:

  • a standard file name, in which case the data is read from a file in TFS.

  • a hexadecimal address and size formatted as 0xADDR,SIZE . This syntax supports the ability to use a TFTP client to retrieve a raw block of memory from the target. (Once again, the syntax does preclude certain filenames).

After determining the address and size of the data to be transferred, the code checks the mode. In this direction, only octet is supported. Finally, state is logged, the first data block is sent, and the monitor enters a wait state, pending an incoming TFTP_ACK opcode.

Listing 10.7: Receiving a Data Packet.
image from book
 case htons(TFTP_DAT):         block = ntohs(*(ushort *)(tftpp+2));         count = ntohs(uhdr->uh_ulen) - (sizeof(struct Udphdr)+4);         if (EtherVerbose & SHOW_TFTP)             printf("  Rcvd TFTP_DAT (%d,blk=%d)\n",count,block);         /* This TFTP_DAT may be from a local "get" request... */         if (TftpState == TFTPSENTRRQ) {             tftpLastblock = 0;             if (block == 1) {                 TftpRmtPort = ntohs(uhdr->uh_sport);                 tftpGotoState(TFTPACTIVE);             }             else {                 SendTFTPErr(ehdr,0,"invalid block",1);                 return(0);             }         }         /* Since we don't ACK the final TFTP_DAT from the server until after          * the file has been written, it is possible that we will receive          * a re-transmitted TFTP_DAT from the server.  This is ignored by          * Sending another ACK...          */         else if ((TftpState == TFTPIDLE) && (block == tftpLastblock)) {             SendTFTPAck(ehdr,block);                 if (EtherVerbose & SHOW_TFTP)                 printf("  (packet ignored)\n");             return(0);                       }         else if (TftpState != TFTPACTIVE) {             SendTFTPErr(ehdr,0,"invalid state",1);             return(0);         }         if (ntohs(uhdr->uh_sport) != TftpRmtPort) {             SendTFTPErr(ehdr,0,"invalid source port",0);             return(0);         }         if (block == tftpLastblock) {   /* If block didn't increment, assume */             SendTFTPAck(ehdr,block);    /* retry.  Ack it and return here.   */             return(0);                  /* If block != tftpLastblock+1,      */         }                               /* return an error, and quit now.    */         else if (block != tftpLastblock+1) {             SendTFTPErr(ehdr,0,"Unexpected block number",1);             TftpCount = -1;             return(0);         }         TftpCount += count;         oaddr = TftpAddr;         tftpLastblock = block;         data = (uchar *)(tftpp+4);         /* If count is less than TFTP_DATAMAX, this must be the last          * packet of the transfer, so clean up state here.          */         if (count < TFTP_DATAMAX) {             tftpGotoState(TFTPIDLE);         } 
image from book
 

Listing 10.7 shows how TFTP_DAT opcodes are handled. To determine which end initiated the transfer, the code tests TftpState . If this state variable has been set to TFTPSENTRRQ then the current TFTP_DAT packet is the result of the target TFTP client issuing a "tftp get" to a remote server. Otherwise the data packet was sent in response to a TFTP_WRQ from a remote client. The code must also cover the case of the remote machine sending a duplicate TFTP_DAT . Often packets are retransmitted because a write to TFS is so large that the remote host times out before the target can finish transferring it to flash. Note that the receiving computer does not send the ACK until after the file has been successfully written to TFS; hence, large files can trigger retransmissions. The final state check just makes sure that the system is already in the active mode waiting for data. Further sanity checks verify that the host sending the data is the same host that sent the RRQ previously. The receiving code also verifies the block number.

Listing 10.8: Saving the Data.
image from book
 /* Copy data from enet buffer to TftpAddr location... */         tmpcount = count;         while(tmpcount) {                    if (TftpWrqMode == MODE_NETASCII) {                 if (*data == 0x0d) {                     data++;                     tmpcount--;                     TftpChopCount++;                     continue;                 }             }                              *TftpAddr = *data;             if (*TftpAddr != *data) {                 sprintf(msg,"Write error at 0x%lx",(ulong)TftpAddr);                 SendTFTPErr(ehdr,0,msg,1);                 TftpCount = -1;                 return(0);             }             TftpAddr++;             data++;             tmpcount--;         }         /* If the transfer is complete and TftpTfsFname[0] is non-zero,          * then write the data to the specified TFS file... Note that a          * comma in the filename is used to find the start of (if any)          * the TFS flags string.  A second comma, marks the info field.          */         if ((count < TFTP_DATAMAX) && (TftpTfsFname[0])) {             char *fcomma, *icomma, *flags, *info;             int err;             info = (char *)0;             flags = (char *)0;             fcomma = strchr(TftpTfsFname,',');             if (fcomma) {                 icomma = strchr(fcomma+1,',');                 if (icomma) {                     *icomma = 0;                     info = icomma+1;                 }                 if (tfsctrl(TFS_FATOB,(long)(fcomma+1),0) != -1) {                     *fcomma = 0;                     flags = fcomma+1;                 }                 else {                     SendTFTPErr(ehdr,0,"Invalid flag spec.",1);                     TftpTfsFname[0] = 0;                     break;                 }             }             if ((EtherVerbose & SHOW_TFTP)  (!MFLAGS_NOTFTPPRN()))                 printf("TFTP adding file: '%s' to TFS.\n",TftpTfsFname);             err = tfsadd(TftpTfsFname,info,flags,                 (char *)getAppRamStart(),TftpCount+1-TftpChopCount);             if (err != TFS_OKAY) {                 sprintf(msg,"TFS err: %s",                     (char *)tfsctrl(TFS_ERRMSG,err,0));                 SendTFTPErr(ehdr,0,msg,1);             }             TftpTfsFname[0] = 0;         }         break; 
image from book
 

After all the sanity checks for TFTP_DAT state, the code in Listing 10.8 can assume that it is time to start the transfer of data from Ethernet packet buffer space to some other target memory. If netascii mode is set, 0x0d ( \r ) characters are dropped as necessary. An incoming packet size less than the max size of TFTP_DATAMAX (512) signals the end of the transfer. If appropriate, the data is transferred to a file in TFS.

Note 

To support TFSs flags and info fields, the filename can be comma-delimited as follows: filename , flags , info ; where filename is the name of the file, flags is a string with all of the flags (or attributes) associated with the file, and info is the information field associated with the TFS file (whitespace is illegal in this comma-delimited string). This format allows a standard TFTP client to specify a destination file (in TFS) with flags and info fields included in the string. The only (reasonable) limitation is that the comma must be used as a field delimiter .

Listing 10.9: Processing and ACK .
image from book
 case htons(TFTP_ACK):         block = ntohs(*(ushort *)(tftpp+2));         if (TftpState != TFTPACTIVE) {             SendTFTPErr(ehdr,0,                "Illegal server state for incoming TFTP_ACK",1);             return(0);         }         if (EtherVerbose & SHOW_TFTP)             printf("  Rcvd TFTP_ACK (blk#%d)\n",block);         if (block == tftpLastblock) {             if (TftpCount > TFTP_DATAMAX) {                 TftpCount -= TFTP_DATAMAX;                 TftpAddr += TFTP_DATAMAX;                 SendTFTPData(ehdr,block+1,TftpAddr,TftpCount);                 tftpLastblock++;             }             else if (TftpCount == TFTP_DATAMAX) {                 TftpCount = 0;                 tftpGotoState(TFTPIDLE);                 SendTFTPData(ehdr,block+1,TftpAddr,0);                 tftpLastblock++;             }             else {                 TftpAddr += TftpCount;                 TftpCount = 0;                 tftpGotoState(TFTPIDLE);             }         }         else if (block == tftpLastblock-1) {             SendTFTPData(ehdr,block+1,TftpAddr,TftpCount);         }         else {             SendTFTPErr(ehdr,0,"Blockno confused",1);             TftpCount = -1;             return(0);         }         return(0); 
image from book
 

The TFTP_ACK acknowledgment notifies the recipient that the previous packet was received and that the other end is ready for another packet. Thus, each ACK potentially triggers another packet transmission. Like the other handlers, this code begins with sanity checks. If all is well, the code then decides whether enough data remains to fill a packet. If not, this packet is the last. Note that a final packet of exactly 512 bytes is processed as a special case. The value in TftpCount keeps track of how much data is left to be sent, and TftpAddr is the location from which the data is to be sent. If the block number is correct, the handler sends a new packet. If the block number is one less than expected, then the code resends the previous packet. All other block numbers trigger an error response.

Listing 10.10: Handling Errors.
image from book
 case htons(TFTP_ERR):         errcode = ntohs(*(ushort *)(tftpp+2));         errstring = tftpp+4;         if (EtherVerbose & SHOW_TFTP)             printf("  Rcvd TFTP_ERR #%d (%s)\n",errcode,errstring);         TftpCount = -1;         tftpGotoState(TFTPERROR);         strncpy(TftpErrString,errstring,sizeof(TftpErrString)-1);         TftpErrString[sizeof(TftpErrString)-1] = 0;         return(0);     default:         if (EtherVerbose & SHOW_TFTP)             printf("  Rcvd <%04x> unknown TFTP opcode\n", opcode);         SendTFTPErr(ehdr,0,"Unexpected opcode received.",1);         TftpCount = -1;         return(-1);     }     SendTFTPAck(ehdr,block);     return(0); } 
image from book
 

The TFTP_ERR opcode requires very little programming. As shown in Listing 10.10, the header merely changes the state to error, records the incoming error, and prints an error message to the console. Listing 10.10 also shows how the default handler deals with an unexpected opcode.



Embedded Systems Firmware Demystified
Embedded Systems Firmware Demystified (With CD-ROM)
ISBN: 1578200997
EAN: 2147483647
Year: 2002
Pages: 118
Authors: Ed Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net