WUMP Programming Project

This program is to implement the BUMP client in java (or C#). Those registered for 343 are to do this with window size 1; those registered for 443 should implement a more-or-less arbitrary window size. Here is an overview of the three WUMP protocols (BUMP, HUMP, and CHUMP).

For the Java version, here are the files wumppkt.java, containing the packet format classes, and wclient.java, which contains an outline of the actual program. There is also a C# version, in wumppkt.cs and wclient.cs. For either language, only the wclient file should be modified; you should not have to make changes to wumppkt.

Your assignment is to do the following, by modifying and extending the wclient outline file:

  1. Implement the basic transfer:
  2. Add all appropriate packet sanity checks: timeouts, host/port, size, opcode, and block number
  3. Generate output. The transferred file is to be written to System.out. A status message about every packet (listing size and block number) is to be written to System.err. Do not confuse these!
  4. Terminate after a packet of size less than 512 is received
  5. Implement an appropriate "dallying" strategy
  6. send an ERROR packet if it receives a packet from the wrong port. The appropriate ERRCODE in this case is EBADPORT.
  7. If required, implement support for window sizes > 1.

It is helpful to view the description in terms of the state diagram. We will use the states UNLATCHED, ESTABLISHED and DALLY used to describe TFTP in intronetworks.cs.luc.edu/current/html/udp.html#tftp-states.

UNLATCHED   
    Transition to ESTABLISHED on receipt of a valid Data[1] packet (correct source IP address, etc)
    Save the source port as latchport

ESTABLISHED
    At any point we are expecting block expected_block
    If we receive a valid full-sized Data[N] packet with N==expected_block, increment expected_block.
    The source port must match latchport.
    If we receive a valid Data[N] with N==expected_block and with size < 512 bytes, transition to DALLY

DALLY
    If we receive a valid Data[N] from the correct port, with N == expected_block, resend the last ACK
    Exit this state after sufficient elapsed time

An outline of the program main loop, with notes, is available here

. I recommend that you implement this in phases, as follows.
  1. Implement the two steps above under "basic transfer". These are also discussed below. This will mean that the vanilla transfer completes.
  2. For each data packet received, write the data to System.out. (This is now done for you.)
  3. Add sanity checks, for (in order) host/port, packet size, opcode, and block number.
  4. Complete the process of latching on to the new port by making sure that the first packet meets all the tests (sanity checks) for Data[1] before you save its port number. You should also make sure the client stops when a packet with less than 512 bytes of data is received.
  5. Handle timeouts, by retransmitting the most recently sent packet when the elapsed time exceeds a certain amount (2 seconds?). One way to do this is to keep a DatagramPacket variable LastSent, which can either be reqDG or ackDG, and just resend LastSent. Note that the response to an InterruptedIOException, a "true" timeout, will simply be to continue the loop again.
  6. Add support for an dallying and error packets. After the client has received the file, dallying means to wait something like two timeout intervals (or more) to see if the final data packet is retransmitted. If it is, it means that the final ACK was lost. The dally period gives the client an opportunity to resend the final ACK. Error packets are to be sent to any sender of an apparent data packet that comes from the wrong port.

You can test your program by contacting the server, ulam.cs.luc.edu, and requesting files. If you contact on port 4715, you get a response from a new port (the standard behavior). This doesn't go through NAT firewalls, so if you contact ulam2 on port 4716 you get a response from the same port. No matter what file you ask for, the file you get is always the same. However, by asking port 4715 for the following names, you get the indicated behavior:

vanilla   Normal transfer

lose      Lose everything after the first windowful (min 3). It will be retransmitted when you retransmit the previous ACK.
spray    Constant barrage of data[1]. Implies LOSE too. In this case, no timeout events will occur; you must check for elapsed time.

delay    Delays sending packet 1, prompting a duplicate REQ and thus results in multiple server instances on multiple ports.

reorder  Sends the first windowful in the wrong order.


dupdata2
DATA[2] is sent twice
losedata2
DATA[2] is lost on initial send, until you resend ACK[1]
marspacket
A packet from the wrong port (a "martian" port) arrives
badopcode
a packet arrives with an incorrect opcode
nofile
you get an error packet with a NO FILE error code.

At this point, the only ones that work on port 4716 are vanilla, lose2 (losedata2) and dup2 (dupdata2).

The Basic Transfer

In order to get the basic vanilla transfer working, there are two things you need to do, as outlined above:

If you run the existing wclient.java (with wumppkt.java), and you have network access that doesn't block the necessary ports, your output should look something like this:

Looking up address of ulam.cs.luc.edu... got it!
req size = 12, filename=vanilla
rec'd packet: len=520; proto=1; opcode=2; src=(10.0.0.1/60000); time=4
         DATA packet blocknum = 1
An Assessment of the Programming Language Pascal
by Niklaus Wirth, developer of Pascal

1. What is reliable software?
Reliable is the attribute for a person, an organization, or a mechanism
that you can trust, that you can depend on, that is worthy of your
confidence.  For example, a reliable clock is one that indicates
accurate time even during an earthquake, a reliable railway system is
one where trains run punctually even during a snowstorm, a reliable
bridge is a bridge that doesn't crack even under heahard timeout
hard timeout
hard timeout
hard timeout
hard timeout
...

What's going on here is that the server sent you Data[1], but your ACK didn't go to the right place, so it never sent you Data[2].

If you don't get Data[1], that is, you got nothing but error and status messages, try uncommenting the SAMEPORT line, because most likely a NAT router is blocking the Data[1] from a new port. If you do this, you will get both Data[1] and Data[2], because your ACK[1] now is going to the correct port (4715). But you won't get past Data[2], because you are not yet ever sending ACK[2]. You will probably get multiple Data[2] packets, because you will resend ACK[1] multiple times and each one will trigger a new Data[2].

The first step is to fix the ACK port, by appropriate placement of something like

ackDG.setPort(thePort);

(In general, you probably only want to set something like latchport, on receipt of the first Data[1], as part of the process of latching on to the initial port. However, at this point it won't matter if you set it on every packet.) You can put this in with the other ackDG settings. At this point, in the SERVERPORT case you should now get Data[1] and Data[2]. Because your repeated ACK[1]'s are now going to the correct port, you should get multiple Data[2]'s, because each time you send ACK[1] the server will respond with Data[2]. This behavior is quite similar, overall, to that of the SAMEPORT setting without the above fix.

(If you're using the SAMEPORT case because you're at home and you have a NAT router, that's fine, but you should still make the above fix anyway, and then come in to Loyola to verify that the SERVERPORT case works properly. Having the SERVERPORT case work is a requirement.)

The second step is to increment expected_block, so that the arrival of Data[2] results in ACK[2] rather than ACK[1]. This is done with

expected_block++;

You can put this anywhere after the line

ack = wp.new ACK(wumppkt.BUMPPROTO, expected_block);

The line above creates an ack with the current expected_block, and you want to update expected_block for the next block. Alternatively, you can place a line

expected_block = data.blocknum();

before the ack creation, because this sets expected_block to the block number of the block that just arrived.

At this point you should get the entire file (11 blocks), with timeouts at the end because you are not properly handling the end-of-transmission case (last block has < 512 bytes of data). Because repeated ACK[11]'s don't trigger any new transmissions at the server side (why?), you won't get multiple copies of the last packet.

In general, you should verify that the arriving block number matches expected_block, as part of other sanity checks, before incrementing the latter. But you can worry about that later.

The next steps are to implement the sanity checks. The most important are to check the incoming port and also the packet's block number (to see that it matches expected_block), but you should also check the packet's IP address and size and opcode (watch the order!).

Extra-Credit Options

Here are a few options for earning extra credit. In order to receive extra credit, your project must be submitted by the due date. You must also make it clear, near the top of your wclient.java file, that you are submitting an extra-credit option. (I may add to this list if I can think of others.)

1. For undergrads only: implement sliding windows. A simple implementation is outlined in the Sliding Windows chapter of my book.

2. Implement HUMP. This should work through all NAT firewalls, and yet still have a new child port created for each connection. HUMP is not particularly hard: after you send the REQ, the first packet you get (which will come from the original server port) will be the handoff, which tells you the handoff port. You then respond with ACK[0] to the handoff port on the server, which then responds with Data[1].

If the handoff packet is lost, the HUMP connection fails. I originally thought this was a problem, but it seems to be a minor one compared to the impact of NAT.

Once you get basic WUMP working, HUMP involves

The main difference is waiting for the handoff instead of Data[1].

3. Implement adaptive timeout. That is, measure the RTT of each packet, and create a running average RTTmean, and then set the timeout interval to, say, 2*RTTmean. You should probably set a minimum timeout of, say, 500 ms.

If you're interested in this option, let me know and I'll create a larger file to use for testing.