[p2p-hackers] UDP file transfer protocol peer review

David Barrett dbarrett at quinthar.com
Sat Jul 23 04:12:23 UTC 2005


In light of of my previous question on link speed identification of a 
UDP file-transfer protocol, I'd like to share my overall protocol and 
ask for feedback on how to improve it.  Here's a (slightly long) 
overview of the protocol, followed by specific questions at the end:


--- Rationale ---
I'm using SIP and RTP as the basis for a P2P system that includes (among 
other things) VoIP and a file transfer feature.  To simplify my 
networking layer, I'm using custom RTP profiles for all non-SIP (ie, 
non-signaling) traffic.  Thus I'm creating a simple file transfer 
profile for RTP.


--- Terminology ---
- server = sending peer
- client = receiving peer


--- Priorities ---
The overall priorities for my file transfer protocol are as follows:
- Fast link speed discovery (to maximize file transfer speed)
- Client-side bandwidth management (to free bandwidth for VoIP)
- High efficiency (minimal bandwidth lost to overhead)
- Minimal fragmentation (all packets fit within UDP MTU)
- Play nice with TCP and other applications


--- Protocol Overview ---
The overall protocol works as follows:
1) Client sends GETINFO to the server
2) Server responds with INFO, which includes the length of the file, as 
well as a strong hash
3) Client send GETBLOCKS to the server, containing a bit vector 
describing which blocks the client hasn't yet received, as well as a 
rate (in blocks per second) at which the server should begin sending them
4) Server sends a series of BLOCK packets to the client, each of which 
contains a block identifier, sequence number, and 1024 bytes of the file 
(except for the final block, which might contain less)
5) Throughout the transfer, the client periodically adjusts the download 
rate by sending CHANGERATE requests to the server.
6) Throughout the transfer, the client occasionally sends an updated bit 
  vector to the server in a new GETBLOCKS request.
7) At the end of the transfer, the client sends DONE and verifies it 
received the file correctly by testing the strong hash.


--- Lessons from TCP ---
I'd like to model the protocol on TCP as much as possible.  But a direct 
application can't be done for at least two reasons:
- TCP puts the server in charge of choosing the send rate (advised by 
the client), while I need to have the client decide (ie, the client 
needs the ability to instantly reduce file transfer bandwidth to free up 
room for an incoming VoIP stream about which the server has no knowledge).
- TCP uses immediate, per-packet ACKs, which are overkill for an 
any-order bulk transfer.  (Upstream bandwidth is an absolute premium so 
I need to conserve wherever possible.)

Regardless, I like the TCP concepts of "slow start" and "congestion 
avoidance", and I'd like to apply them here.


--- Adjusting the Send Rate ---
Whenever the client determines data is being sent too fast or too slow, 
the client calculates a new send rate and requests it using CHANGERATE. 
  If increasing, it can either use the "slow start" or "congestion 
avoidance" formula.  If decreasing, it uses the "decrease" formula. 
These are defined as follows:
- floor                   = Rate after the last decrease
- rate                    = Current send rate
- span                    = rate - floor
- newRate_slowStart       = rate + span
- newRate_congestionAvoid = rate + CONSTANT
- newRate_decrease        = floor + span*FRAC

The "floor" is a deviation from TCP (which always uses a zero floor), 
and is actually the top value of a stack that is adjusted as follows:
- When decreasing after period of increase, push "newRate" on top
- When decreasing twice in a row, pop the stack
- If the stack is empty, reset to initial values

The net result should ideally produce a graph like this:

    |- - - -|- -/|-/|/|/|/- - link speed
    |       |  / |/
    |      || /
   r|      ||/
   a|      |
   t|     |
   e|     |
    |    /
    |__-^
    +-------------------------- time
    0       a    b  c d e ...

(As for the discontinuities at a-e, I'll use the notation rate(a) to 
mean the rate just before, and rate(a') to mean the adjusted rate 
immediately after.)

0-a: Slow start, exponential increase from scratch
a-b: Congestion avoid, CONSTANT slope starting from FRAC*rate(a)
b-c: Congestion avoid from rate(a')+FRAC*(rate(b)-rate(a'))
c-d: Congestion avoid from rate(b')+FRAC*(rate(c)-rate(b'))
d-e: Congestion avoid from rate(c')+FRAC*(rate(d)-rate(c'))

In other words, it starts with exponential increase to quickly get in 
the right ballpark, and then switches to linear increase while it zeros 
in on the link speed.  Eventually it'll oscillate with low magnitude 
right around the actual rate.


--- Rate Change Trigger ---
One big question is "how does the client decide when to change rates?" 
I don't have a good answer here, and I'd love your advice.  It seems 
that there are a few options to decide when to increase:
a) When it receives COUNT packets without packet loss
b) When it receives some duration of packets without packet loss
c) When it downloads at the full requested rate for some period

Likewise, there is a similar list of options about when to decrease:
a) When it detects a single packet loss
b) When it is unable to download at the requested rate for some period

I really don't know which to use.  I've experimented with "increase:c" 
and "decrease:b", but I find the download rate is so erratic that it 
requires a long sample period to average out, which in turn makes the 
whole system respond very slowly.

I'm considering using "increase:a" and "decrease:a" as it seems to work 
well for TCP, but I'm concerned about the impact of wireless networks 
(which I'm told have high packet loss).


--- Questions for the Reader ---
So, with all that played out, here are my specific questions for you:
1) What's a good value for CONSTANT?
2) What's a good value for FRAC?
3) What's a good value for COUNT?
4) Which option should I use to trigger increases?
5) Which option should I use to trigger decreases?
6) What are the ramifications using a non-zero "floor" (ie, being more 
aggressive than TCP)?
7) What would the ramifications be for using only "slow start", and 
skipping "collision avoidance" altogether?

Likewise, I'm sure there are plenty of areas that deserve additional 
scrutiny, so feel encouraged to point them out.

Thanks, I look forward to hearing your thoughts!

-david



More information about the P2p-hackers mailing list