uTP Micro Transport Protocol Documented

While updating my bittorrent library, I found that the uTP (Micro Transport Protocol) documentation is quite lacking, unless you already know how the protocol works, so here is my attempt to expand on the documentation that already exists in an effort to provide more information on implementing uTP.

Information from BEP 29, bittorrent/libutp at github and from reverse engineering based on Wireshark UDP dumps.

Notes

uTP Packet Header Format

All fields are in network byte order (big endian).

0       4       8               16              24              32 (bits)
+-------+-------+---------------+---------------+---------------+
| type  | ver   | extension     | connection_id                 |
+-------+-------+---------------+---------------+---------------+
| timestamp_microseconds                                        |
+---------------+---------------+---------------+---------------+
| timestamp_difference_microseconds                             |
+---------------+---------------+---------------+---------------+
| wnd_size                                                      |
+---------------+---------------+---------------+---------------+
| seq_nr                        | ack_nr                        |
+---------------+---------------+---------------+---------------+

type

packet type, and is one of the following 4 bit ints

Name id Description
ST_DATA 0 regular data packet, always has a payload
ST_FIN 1 Finalize the connection, is the last packet. seq_nr of this packet will be the last packet expected (though earlier packets may still be in flight)
ST_STATE 2 State packet, used to transmit an acknowledgement of a packet, does not increase the seq_nr
ST_RESET 3 Force closes the connection
ST_SYN 4 Connect/Sync packet, seq_nr is set to 1, connection_id is randomised, all subsequent packets sent on this connection are sent with the connection_id + 1

version

protocol version, is always set to 1 and stored as a 4 bit int

connection_id

This is a random, unique, 16 bit int, identifying all the packets that belong to the same connection. Initial packet is sent with this number, all subsequent packets are sent with connection_id + 1

extension

not currently used in this library, always set to a 8 bit int of 0

timestamp_microseconds

The timestamp in microseconds when the packet was sent, this should be as close to the actual transmit time as possible

timestamp_difference_microseconds

Initialised as 0 for the ST_SYN packet, subsequent packets set this to the current timestamp in microseconds, minus the timestamp_microseconds of the last received packet.

wnd_size

32 bit int specificied in bytes. This is the number of bytes currently sent, but not yet ACK-ed with a ST_STATE. When sending any packets to the remote peer, this must be set to the number of bytes left in the sockets receive buffer.

seq_nr

16 bit int, initialised to 1 or any random positive number. incremented by 1 for each packet sent except for when receiving ST_STATE, serves as an order number for the receiving end.

ack_nr

The seq_nr from the last packet received, unless it is the first ST_SYN, then it is seq_nr - 1.

Congestion Control

Congestion Control uses ledbat congestion control and uses a calculated max_window and also uses wnd_size where max_window is initialised to INT_MAX.

const CCONTROL_TARGET = 100 * 1000 // 100ms in microseconds
const MAX_CWND_INCREASE_BYTES_PER_RTT = 3000
const MIN_WINDOW_SIZE = 10
const MAX_WINDOW_DECAY = 100 * 1000

var current_msec = current_microseconds()

var target = CCONTROL_TARGET
if (target <= 0) target = CCONTROL_TARGET

var min_rtt = min(min_rtt, current_msec - last_received_packet.timestamp)
var our_delay = min(min(our_history), min_rtt)
var off_target = target - our_delay
var window_factor = min(bytes_acked, max_window) / max(max_window, bytes_acked)
var delay_factor = off_target / target
var scaled_gain = MAX_CWND_INCREASE_BYTES_PER_RTT * window_factor * delay_factor

if (scaled_gain + max_window < MIN_WINDOW_SIZE) {
    max_window = MIN_WINDOW_SIZE
} else {
    max_window = max_window + scaled_gain
}

max_window = max_window < MIN_WINDOW_SIZE ? MIN_WINDOW_SIZE : (max_window > socket_send_buff_size ? socket_send_buff_size : max_window) // set new max window
// max_window is now the maximum number of bytes that can be sent with this packet

//can we decay the max_window?
if(current_msec - last_decay_msec >= MAX_WINDOW_DECAY) {
    max_window = max_window * 0.5
	last_decay_msec = current_msec

    if(max_window < MIN_WINDOW_SIZE) {
        max_window = MIN_WINDOW_SIZE
    }
}

Example Transaction

Comments

comments powered by Disqus