Network Programming 2000

Project 3 - TFTP Server
Frequently Asked Questions

Questions:
Using select instead of alarms. Running Server Can I hand in my friends code? Mail mode Concurrency
Solaris TFTP client SIGALRM and BSD No such user Error Code Disk Full Error Code Error Codes
Using Stevens' Code rough list of steps    
Q: Where is the sample tftp server we can use to get the hang of using the client?

A: monica.cs.rpi.edu has a tftp server running on the default tftp port. There are 3 files you can read, named small, medium and large. You can't "put" any files. Here is an example session showing how to turn on trace mode (shows each datagram sent and received):
/home1/b/hollingd  tftp monica.cs.rpi.edu
tftp> ?
Commands may be abbreviated.  Commands are:

connect         connect to remote tftp
mode            set file transfer mode
put             send file
get             receive file
quit            exit tftp
verbose         toggle verbose mode
trace           toggle packet tracing
status          show current status
binary          set mode to octet
ascii           set mode to netascii
rexmt           set per-packet retransmission timeout
timeout         set total retransmission timeout
?               print help information
tftp> trace
Packet tracing on.
tftp> verbose
Verbose mode on.
tftp> binary
mode set to octet
tftp> get small
getting from monica.cs.rpi.edu:small to small [octet]
sent RRQ <file=small, mode=octet>
received DATA <block=1, 512 bytes>
sent ACK <block=1>
received DATA <block=2, 512 bytes>
sent ACK <block=2>
received DATA <block=3, 0 bytes>
Received 1024 bytes in 0.5 seconds [16384 bits/sec]
tftp> 

Q: Can I use select to take care of the timeouts?

A: Yes! (and it makes things much simpler and probably more portable).

Q: I'm working on this with my friend/roommate/spouse/valentine/hired-gun, is it OK if we hand in very similar code?

A: No! Too much of this on Project 2!!! You many not share code in any way. You can discuss the project all you want, but do not show your code to anyone!!! Not convinced? Consider this:

Top 5 reasons to do the project yourself:

  1. Cause Dave said "please".
  1. Dave has a fancy program-comparison checking program. I think he lifed it from someone else...
  1. Dave is in a bad mood, he is stuck using Windows98 until he gets a real computer.
  1. Dave is known across campus as "Mr. Flunk-the-Cheaters"!
  1. You might actually learn something useful.

Q: Do we have to support "mail" transfer mode?

A: No! I don't think any clients support it!

Q: Would the server be considered to be concurrent if we just did not call connect() on the socket and then handled a mixture of incoming packets on the same udp socket? Is this even possible? Since udp is connectionless, I figure that two clients can be talking to the same server at the same time, as long as the server is capable of distingushing between the two.

A: Sure, although there is support for handling this problem built in to TFTP. The server is expected to respond to the initial request from a new UDP port number - all subsequent communication with the client is done through this new dedicated port. (Check out section 4. Initial Connection Protocol of RFC 1350 - TFTP for more information on this.). So, in fact - the server can call connect. BUT this means the server has to worry about multiple sockets, since if you do it all in a single process the server will need to have a seperate socket for each client.

Of course you don't _need_ to use a different port for each client, the client just assumes that the port number from which the initial reply comes is the port number it should be sending to (not the well known port). If you want to try to do the multiplexing yourself you are free to try.

The "simple" way to provide concurrency is just to fork() a child for each client and to have each child create a new UDP socket (port) that is used to handle the client. All incoming requests will be sent to the well known port that the parent process is looking at. The only thing to watch out for is that the parent process cleans up after all the child processes (calls wait()).

Q: Something isn't working right. I'm running a Solaris TFTP client and it says "Bad Port Number".

A: There is something in the Solaris tftp client that refuses to use port numbers greater than 32767 (the code is probably treating the 16 bit port number as signed, which is a mistake!). Use monica for running the tftp client or try requesting a specific port number (less than 32767)

Q: On FreeBSD systems (monica), system calls like read, write, sendto and recvfrom are automatically restarted by the kernel - so I can't interrupt them using alarm() and SIGALRM. Can I tell the kernel to interrupt these system calls so they return and I can check for EINTR?

A: You need to use the POSIX signal handling routines (sigaction() instead of signal()), and to specify that you want SIGALRM to interrupt, not to restart slow system calls. Check sections 5.8 and 5.9 in the text for more information.

This works for me:


/* ALARM handler does nothing */
static void alrm_handler( int sig) {
}

    ...
   struct sigaction act,old;


   act.sa_handler=alrm_handler;
   sigemptyset(&act.sa_mask);
   act.sa_flags=0;
   sigaction(SIGALRM,&act,&old);

   /* SIGALRM will now interrupt slow system calls */

Q: What does the seventh error code mean ? (No such a user).

A: I haven't found any use for this error code either. I assume this was included when "mail" transfer mode was supported (if it ever was).

Q: Do we need to check third error code (Disk full or allocation exceeded) when we write the file?

A: If there is an error while writing the file, you should send back an error message - this one sounds reasonable. I don't expect you to actually determine the exact cause of an error while writing to a file, so you could assume the cause is that the disk is full. The important thing is that you notify the client that something went wrong and the file was not (completely) saved.

Q: It there any way that you could give me a list of errors that the tftp server has to handle. This would be very helpful to determine if the server I created runs according to your standards.

A: OK:

  • For a RRQ, if you can't open the file you need to send either a 1 (file not found) or 2 (access violation). I don't care if you determine the actual cause of the open failure, either error code is fine.
  • If the open fails for a WRQ you should return 2 (access violation).
  • If the open for a WRQ was OK, but later one of the writes fails, you should return 3 (Disk full) - you don't need to determine if this is really the problem.
  • Any unknown opcode should result in an error code 4. If you decide not to overwrite files (when receiving a WRQ) you can return an error code 6 (file already exists) - but you can overwrite if you want to.
  • There is no condition I can think of that should result in error code 7 (no such user).
Q: I based my code on the tftp server in the first edition of Unix Network Programming by R. Stevens, so it looks similar. This is ok as long as we understand all the portions of the code -- right?

A: Yes, although if you really did the whole finite state machine thing, I will make sure you understand every line (and doing so is probably more work than simply writing your own server).

Q:In the last FAQ you gave a list of the steps required to make a 'minimal' proxy server, could you have a similar list for TFTP?

A: OK, but DISCLAIMER: this is the general idea, but there are many details not included. For example, I don't say what to do if you get something you are not looking for, or how long to wait before retransmitting, etc.

  • Create and bind a udp socket, print out the port number.
  • wait for a request (recvfrom on the "well known" udp socket).
  • You can handle concurrency anyway you want, one way is to now fork off a child to handle this request.
  • look at the opcode (first 2 bytes as a network byte order short) in the request. If the request is:
    • RRQ
      • Create a new udp socket to use with this client.
      • get the filename from the request and try to open the file. If the open fails send back an error (file does not exist) and we are done with the client. NOTE: see later section on lingering.
      • open succeeds - read first 512 bytes, package up as a TFTP message with block set to 1 and send it.
      • while (last block size == 512)
        • wait for ACK for last data sent. don't wait too long.
        • if timeout (no ACK) and total time is not too long, resend the data. If time too long - we are done with the client.
        • Got the ACK - now read the next 512 bytes of the file
      • transaction complete - close the file. done with client (see note on lingering).
    • WRQ
      • Create a new udp socket to use with this client.
      • get the filename from the request and try to open the file for writing. If the open fails send back an error (access violation) and we are done with the client. NOTE: see later section on lingering.
      • open succeeds : send an ACK #0 to the client.
      • while (last block rcvd == 512 bytes of data)
        • wait for next bock of data from client. don't wait too long.
        • if timeout (no DATA) and total time is not too long, resend the last ACK. If time too long - we are done with the client.
        • Got the DATA - write to the file. If write error, return a error to the client (disk full) and consider the client done.
        • Send ACK for data just received.
      • transaction complete - close the file and done with client (see note on lingering).
NOTE ON LINGERING: Once the server has completed a transaction (either request has completed or there has been an error) you may want to wait for a while to gobble up any stray datagrams coming from the client. See section 6. Normal Termination in the TFTP RFC for more information.