Network Programming Spring 99

Project 3 - TFTP Server
Frequently Asked Questions

Questions:
Concurrency Solaris TFTP client SIGALRM and BSD Supporting Octet transfers
No such user Error Code Disk Full Error Code Error Codes Using Stevens' Code rough list of steps
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 783 - 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 client or try requesting a specific port number (less than 32767)

Q: On FreeBSD systems (monica and ken), 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: Whenever I got a request, I will check whether it is an octet request. If it is not, we can simply ingore that. Right?

A: You can ignore anything that is not octet, you can also ignore the file type and always assume it is octet.

When testing - you can force all requests to be octet by using "binary" command on any tftp client.

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

A: I haven't found any use for this error code either.

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.