RFC 7574
Peer-to-Peer Streaming Peer Protocol (PPSPP)
RFC 7574: Peer-to-Peer Streaming Peer Protocol (PPSPP) ๐
okay bestie, this is the title block tea โ ๐
- Publisher: the Internet Engineering Task Force (IETF) ๐
- RFC number: 7574
- Title: Peer-to-Peer Streaming Peer Protocol (PPSPP)
- Authors: A. Bakker (Vrije Universiteit Amsterdam), R. Petrocco, and V. Grishchenko (Technische Universiteit Delft) ๐ฏ
- Category: Standards Track ๐ฅ
- ISSN: 2070-1721
- Date: July 2015 ๐๏ธ
Abstract โจ
so PPSPP is a protocol for getting the same content to a whole group of people who are interested in it, and it does this in a streaming way ๐ it works for prerecorded on-demand stuff AND live audio/video, which is so iconic ๐บ
it's built on the peer-to-peer vibe, where the people watching are on equal footing with the servers that first had the content. everyone can potentially give upload bandwidth, so it's a whole community energy thing ๐ซถ it's designed for short time-till-playback so you're not sitting there waiting forever ๐ญ, and it's built to stop malicious peers from wrecking the streams ๐ก๏ธ
it's also super flexible and extensible ๐ฆ it can use different methods to optimize peer uploading, stop freeriding, and work with different peer discovery schemes, like centralized trackers or Distributed Hash Tables (DHTs) ๐ง. it supports multiple methods for content integrity protection and chunk addressing too, which is giving precision ๐
it's a generic protocol that can run on top of different transport protocols. right now it runs on UDP and uses Low Extra Delay Background Transport (LEDBAT) for congestion control ๐ธ honestly the whole thing is so well thought out, I'm obsessed ๐ญ๐ฅ
Status of This Memo โญ
this one is a Standards Track document, so it's an official Internet Standards Track document ๐
it's a product of the Internet Engineering Task Force (IETF), and it represents the consensus of the IETF community. it got public review and was approved for publication by the Internet Engineering Steering Group (IESG). if you want more info on Internet Standards, check Section 2 of RFC 5741 ๐
for the current status of this doc, any errata, and how to send feedback, go to http://www.rfc-editor.org/info/rfc7574 ๐
Copyright Notice ๐
Copyright (c) 2015 IETF Trust and the persons identified as the document authors. All rights reserved. ๐ฉท
this document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (http://trustee.ietf.org/license-info) as they stand on the date of publication. please read these carefully, bestie, because they explain your rights and restrictions for this document ๐
one important bit: Code Components pulled out of this document must include Simplified BSD License text, as described in Section 4.e of the Trust Legal Provisions. they come without warranty, as described in the Simplified BSD License ๐
1. Introduction โจ
bestie ๐ฅบ the paste stops right after the "1. Introduction" heading, so there's no actual Section 1 text for me to rewrite yet. I'm not going to invent spec wording, because implementers rely on every word and a made-up requirement would be a disaster ๐ญ. Could you paste the body of Section 1 (starting at 1.1. Purpose) and any remaining text from the Introduction? Then I'll pick up exactly where we left off ๐
1.1. Purpose ๐
Okay bestie, let's talk about PPSPP, the Peer-to-Peer Streaming Peer Protocol ๐ It's built for getting the same content to a whole group of interested people, streaming style. It handles both prerecorded (on-demand) and live audio/video โจ The big idea is peer-to-peer: the people watching are on equal footing with the servers that started out holding the content, so everyone can potentially contribute upload bandwidth ๐ซถ
PPSPP is designed for short time-till-playback so the end user isn't stuck waiting ๐ญ It also protects streams from malicious peers, and the key to that is a simple way of identifying content through self-certification ๐ฅ Content in PPSPP is identified by one cryptographic hash, the root hash of a Merkle hash tree that gets calculated recursively from the content [MERKLE] [ABMRKL]. This self-certifying hash tree means every peer can directly detect when a malicious peer is trying to spread fake content ๐ต๏ธโโ๏ธ The tree works for both static and live content, and it means only a small amount of information is needed to start a download and to verify incoming chunks. That's how start-up stays fast ๐
PPSPP is also built to be extensible across different transports and use cases ๐ฆ It's a generic protocol that can run directly on top of UDP, TCP, or other protocols. It defines a common set of messages, and those messages can look different on the wire depending on which lower-level protocol is in use ๐ When the lower-level transport allows it, PPSPP can also use different congestion control algorithms.
Right now PPSPP is set to run on top of UDP using LEDBAT for congestion control [RFC6817] โญ LEDBAT is what lets PPSPP keep serving the content after playback finishes (this is called seeding) without disrupting the user, who may have moved on to other tasks on the same network connection ๐
PPSPP is also flexible about how it encourages clients to contribute and prevents freeriding, which means dealing with peers that only download and never upload to anyone ๐ If the defaults don't fit a particular use case, it also allows different schemes for chunk addressing and content integrity protection. It can work with different peer discovery schemes too, such as centralized trackers or fast Distributed Hash Tables [JIM11]. And in this default setup, PPSPP keeps only a small amount of state per peer ๐ง A reference implementation of PPSPP over UDP is available [SWIFTIMPL].
The protocol in this document assumes a peer has already discovered a list of (initial) peers, for example through a centralized tracker [PPSP-TP] ๐ Once a peer has that list, PPSPP lets it connect to other peers, request chunks of content, and discover other peers distributing the same content.
PPSPP's design comes from our research into making BitTorrent [BITTORRENT] suitable for streaming content [P2PWIKI] ๐ Most PPSPP messages have BitTorrent counterparts and vice versa. But PPSPP is specifically targeted at streaming audio/video and optimizes time-till-playback, and it was designed to be more flexible and extensible too ๐ฌ
1.2. Requirements Language ๐
Here's the spicy bit about the key words. In this document, "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" are to be interpreted as described in RFC 2119 [RFC2119] ๐ซถ These are the words that set exactly how strict a rule is, so read them carefully ๐
1.3. Terminology ๐
Okay, time for the vocab list ๐ These are the terms the rest of the spec leans on, so let's get them down:
- message: The basic unit of PPSPP communication. A message looks different on the wire depending on the transport protocol used. Messages are typically multiplexed into a datagram for transmission.
- datagram: A sequence of messages offered as one unit to the underlying transport protocol (UDP, etc.). The datagram is PPSPP's Protocol Data Unit (PDU).
- content: Either a live transmission or a prerecorded multimedia file.
- chunk: The basic unit the content is divided into. For example, a block of N kilobytes. A chunk may be of variable size.
- chunk ID: A unique identifier for a chunk of content (e.g., an integer). Its type depends on the chunk addressing scheme used.
- chunk specification: An expression that denotes one or more chunk IDs.
- chunk addressing scheme: A scheme for identifying chunks and expressing a peer's chunk availability map in a compact way.
- chunk availability map: The set of chunks a peer has successfully downloaded and checked the integrity of.
- bin: A number denoting a specific binary interval of the content (i.e., one or more consecutive chunks) in the bin numbers chunk addressing scheme (see Section 4).
- content integrity protection scheme: A scheme for protecting the integrity of the content while it's being distributed over the peer-to-peer network. It gives receiving peers ways to detect whether a requested chunk has been modified, either maliciously by the sending peer or accidentally in transit.
- hash: The result of applying a cryptographic hash function, more specifically a Modification Detection Code (MDC) [HAC01], such as SHA-256 [FIPS180-4], to a piece of data.
- Merkle hash tree: A tree of hashes whose base is formed by the hashes of the chunks of content. Its higher nodes are calculated by recursively computing the hash of the concatenation of the two child hashes (see Section 5.1).
- root hash: The root in a Merkle hash tree calculated recursively from the content (see Section 5.1).
- munro hash: The hash of a subtree that is the unit of signing in the Unified Merkle Tree content authentication scheme for live streaming (see Section 6.1.2.1).
- swarm: A group of peers participating in the distribution of the same content.
- swarm ID: A unique identifier for a swarm of peers. In PPSPP it's a sequence of bytes. For video on demand with content integrity protection enabled, the identifier is the so-called root hash of a Merkle hash tree over the content. For live streaming, the swarm ID is a public key.
- tracker: An entity that records the addresses of peers participating in a swarm (usually for a set of swarms) and makes this membership information available to other peers on request.
- choking: When Peer A is choking Peer B, it means A is currently not willing to accept requests for content from B.
- seeding: Peer A is said to be seeding when A has downloaded a static content file completely and is now offering it for others to download.
- leeching: Peer A is said to be leeching when A has not completely downloaded a static content file yet, or is not offering to upload it to others.
- channel: A logical connection between two peers. The channel concept lets peers use the same transport address for communicating with different peers.
- channel ID: A unique, randomly chosen identifier for a channel, local to each peer. So the two peers logically connected by a channel each have a different channel ID for that channel.
- heavy payload: A datagram has a heavy payload when it contains
DATAmessages,SIGNED_INTEGRITYmessages, or a large number of smaller messages.
Quick aside: in this document, the prefixes kilo-, mega-, etc., denote base 1024 ๐
2. Overall Operation ๐ธ
The basic unit of communication in PPSPP is the message โจ Multiple messages get multiplexed into a single datagram for transmission. A datagram (and therefore the messages inside it) will look different on the wire depending on the transport protocol used (see Section 8) ๐ฆ
The overall operation of PPSPP is shown in the examples below. They assume the content is static, UDP is used for transport, the Merkle Hash Tree scheme is used for content integrity protection, and a specific policy is used for choosing which chunks to download ๐ง
2.1. Example: Joining a Swarm ๐
Picture this: a user wants to watch a video ๐ฟ To play it, they click the play button on a HTML5 <video> element in their PPSPP-enabled browser. Imagine this element has a PPSPP URL (to be defined elsewhere) that identifies the video as its source. The browser passes this URL to its peer-to-peer streaming protocol handler. Let's call that handler Peer A ๐ Peer A parses the URL to get the transport address of a peer-to-peer streaming protocol tracker, plus the swarm metadata for the content. The tracker address may be optional when a decentralized tracking mechanism is in place. How peers get tracked is outside the scope of this document ๐
Next, Peer A registers with the tracker following the peer-to-peer streaming protocol tracker specification [PPSP-TP] and receives the IP address and port of peers already in the swarm. Let's say Peers B, C, and D ๐ซถ Now PPSPP starts operating. Peer A sends a datagram containing a PPSPP HANDSHAKE message to Peers B, C, and D. This message conveys protocol options. Specifically, Peer A includes the ID of the swarm (part of the swarm metadata) as a protocol option, because the destination peers can listen for multiple swarms on the same transport address ๐ฅ
Peers B and C reply with datagrams containing a PPSPP HANDSHAKE message and one or more HAVE messages. A HAVE message conveys (part of) a peer's chunk availability, so it contains a chunk specification that denotes which chunks of the content Peers B and C have, respectively ๐ฆ Peer D sends a datagram with a HANDSHAKE and HAVE messages, but also a CHOKE message. That CHOKE tells Peer A that Peer D isn't willing to upload chunks to it right now ๐ฅบ
2.2. Example: Exchanging Chunks ๐ฆ
okay so picture this ๐
Peer A sends fresh datagrams to Peers B and C, and each one has REQUEST messages in it. A REQUEST tells the other peer which chunks you want to download, so it carries a chunk specification. The REQUESTs to B and C point at disjoint sets of chunks, so nobody's asking for the same stuff twice ๐
Peers B and C answer with datagrams holding HAVE, DATA, and, in this example, INTEGRITY messages. In the Merkle hash tree content protection scheme (see Section 5.1), the INTEGRITY messages contain all the cryptographic hashes Peer A needs to verify the integrity of the content chunk sent in the DATA message. ๐ With those hashes, Peer A checks that the chunks from B and C are correct against the trusted swarm ID. It's giving security check โ
Peer A also updates its picture of which chunks B and C have, using the info in their HAVE messages. And then it hands the video chunks over to the user's browser for rendering ๐ฌ
After all that processing, Peer A sends a datagram with HAVE messages for the chunks it just got to all its peers ๐ฃ The datagram going to B and C also has an ACK message that acknowledges the chunks received, plus new REQUESTs for more chunks. ACK messages are not used when a reliable transport protocol is used. Now say Peer C notices that Peer A got a chunk from Peer B that Peer C didn't have yet. Peer C's next datagram includes a REQUEST for that chunk. Main character energy for sure ๐
Peer D also sends HAVE messages to Peer A whenever it downloads chunks from other peers ๐ธ When Peer D is willing to accept REQUESTs from Peer A, Peer D sends a datagram with an UNCHOKE message to tell Peer A. If Peer B or C decides to choke Peer A, they send a CHOKE message, and then Peer A should re-request from other peers ๐ Peers B and C may keep sending HAVE, REQUEST, or periodic keep-alive messages, which means Peer A keeps getting HAVE messages from them. Lowkey so wholesome ๐ซถ
Once Peer A has received all the content (the video-on-demand use case), it stops sending messages to all the other peers that have all the content (a.k.a. seeders) ๐ Peer A can also contact the tracker or another source again to get more peer addresses ๐ง
2.3. Example: Leaving a Swarm ๐
To leave a swarm gracefully, Peer A sends a specific HANDSHAKE message to all its peers (see Section 8.4) and deregisters from the tracker following the tracker specification [PPSP-TP]. โจ Peers that get the datagram should remove Peer A from their current peer list. Now if Peer A crashes ungracefully, peers should remove Peer A from their peer list once they detect it no longer sends messages (see Section 3.12). Ghosting is not it, bestie ๐
3. Messages ๐
No error codes or responses are used in the protocol. Absence of any response indicates an error. Invalid messages are discarded, and further communication with the peer SHOULD be stopped. ๐ The rationale is that it's enough to sort peers into good or bad and then only use the good ones ๐ A good peer is one that responds with chunks. A peer that does not respond, or does not respond in time, gets classified as bad. The idea is that, in PPSPP, the content is available from multiple sources (unlike HTTP), so a peer should not invest too much effort trying to get it from one particular source ๐ This good/bad split lets a peer deal with slow, crashed, and (silent) malicious peers. Iconic filtering ๐
Multiple messages MUST be multiplexed into a single datagram for transmission. Messages in a single datagram MUST be processed in the strict order in which they appear in the datagram. If an invalid message is found in a datagram, the remaining messages MUST be discarded. โ ๏ธ
For the sake of simplicity, one swarm of peers deals with one content file or stream only ๐๏ธ There is a single division of the content into chunks that all peers in the swarm adhere to, determined by the content publisher. Distributing a collection of files can be done either by using multiple swarms or by using an external storage mapping from the linear byte space of a single swarm to different files, which is transparent to the protocol. In other words, the audio/video container format used is outside the scope of this document ๐ฆ
3.1. HANDSHAKE ๐ค
For Peer P to establish communication with Peer Q in Swarm S, the peers must first exchange HANDSHAKE messages through a handshake procedure. The initiating Peer P needs to know the metadata of Swarm S, which consists of:
- (a) the swarm ID of the content (see Sections 5.1 and 6),
- (b) the chunk size used,
- (c) the chunk addressing method used,
- (d) the content integrity protection method used, and
- (e) the Merkle hash tree function used (if applicable).
- (f) If automatic content size detection (see Section 5.6) is not used, the content length is also part of the metadata (for static content.)
This document assumes the swarm metadata is obtained from a trusted source ๐ Peer P also needs to know a transport address for Peer Q, which it gets from a peer discovery/tracking protocol.
The payload of the HANDSHAKE message contains a sequence of protocol options. The protocol options encode the swarm metadata just described, so the peers can do an end-to-end check that they're in the right swarm ๐ต๏ธโโ๏ธ The options also encode a number of per-peer configuration parameters. The complete set of protocol options is specified in Section 7. The HANDSHAKE message also contains a channel ID for multiplexing communication and security (see Sections 3.11 and 12.1). A HANDSHAKE message MUST always be the first message in a datagram. ๐ฅ
3.1.1. Handshake Procedure ๐
The handshake procedure for a peer, Peer P, to start communication with another peer, Peer Q, in Swarm S goes like this:
- The first datagram the initiating Peer P sends to Peer Q MUST start with a
HANDSHAKEmessage. ThisHANDSHAKEmessage MUST contain:- A channel ID,
chanP, randomly chosen as specified in Section 12.1. - The metadata of Swarm S, encoded as protocol options, as specified in Section 7. In particular, the initiating Peer P MUST include the swarm ID.
- The capabilities of Peer P, in particular, its supported protocol versions, "Live Discard Window" (in case of a live swarm), and "Supported Messages", encoded as protocol options.
- A channel ID,
This first datagram MUST be prefixed with the (destination) channel ID 0; see Section 3.11. So the datagram ends up with two channel IDs: the destination channel ID prefixed to the datagram, and the channel ID chanP included in the HANDSHAKE message inside the datagram ๐ฏ This datagram MAY also contain some minor additional payload, e.g., HAVE messages to show Peer P's current progress, but it MUST NOT include any heavy payload (defined in Section 1.3), such as a DATA message. Allowing minor payload cuts down the number of initialization round trips, which improves time-till-playback โฉ Forbidding heavy payload prevents an amplification attack (see Section 12.1). Smart and safe ๐ก๏ธ
- The receiving Peer Q checks the
HANDSHAKEmessage from Peer P. If any check by Peer Q fails, or if Peers P and Q are not in the same swarm, Peer Q MUST NOT send aHANDSHAKE(or any other) message back, because the message from Peer P may have been spoofed (see Section 12.1) ๐ โโ๏ธ Otherwise, if Peer Q is interested in communicating with Peer P, Peer Q MUST send a datagram to Peer P that starts with aHANDSHAKEmessage. This replyHANDSHAKEMUST contain:- A channel ID,
chanQ, randomly chosen as specified in Section 12.1. - The metadata of Swarm S, encoded as protocol options, as specified in Section 7. In particular, the responding Peer Q MAY include the swarm ID.
- The capabilities of Peer Q, in particular, its supported protocol versions, its "Live Discard Window" (in case of a live swarm), and "Supported Messages", encoded as protocol options.
- A channel ID,
This reply datagram MUST be prefixed with the channel ID chanP that Peer P sent in its first HANDSHAKE message (see Section 3.11). This reply datagram MAY also contain some minor additional payload, e.g., HAVE messages to show Peer Q's current progress, or REQUEST messages (see Section 3.7), but it MUST NOT include any heavy payload. ๐จ
- The initiating Peer P checks the reply datagram from Peer Q. If the reply datagram is not prefixed with (destination) channel ID
chanP, Peer P MUST discard the datagram. Peer P SHOULD continue to process datagrams from Peer Q that do meet this requirement. This check prevents interference by spoofing (see Section 12.1) ๐ฅท If Peer P's channel ID is echoed back correctly, the initiator Peer P knows the addressed Peer Q really responds. Receipt confirmed ๐ - Next, Peer P checks the
HANDSHAKEmessage in the datagram from Peer Q. If any check by Peer P fails, or Peer P is no longer interested in communicating with Peer Q, Peer P MAY send aHANDSHAKEmessage to tell Peer Q it will cease communication. This closingHANDSHAKEmessage MUST contain an all zeros channel ID and a list of protocol options. The list MUST either be empty or contain the maximum version number Peer P supports, following the min/max versioning scheme defined in [RFC6709], Section 4.1 ๐
The datagram containing this closing HANDSHAKE message MUST be prefixed with the (destination) channel ID chanQ. Peer P MAY also simply cease communication. Ghost mode, activated ๐ป
- If the addressed peer, Peer Q, does not respond to initiating Peer P's first datagram, Peer P MAY resend that datagram until Peer Q is considered dead, according to the rules in Section 3.12 ๐
- If the reply datagram by Peer Q does pass the checks by Peer P, and Peer P wants to keep interacting with Peer Q, Peer P can now send
REQUEST,PEX_REQ, and other messages to Peer Q. Datagrams carrying these messages MUST be prefixed with the channel IDchanQsent by Peer Q. More specifically, because Peer P knows that Peer Q really responds, Peer P MAY start sending Peer Q messages with heavy payload. That means Peer P MAY start responding to anyREQUESTmessages Peer Q may have sent in this first reply datagram withDATAmessages. Hence, chunk transfer can start soon in PPSPP ๐ - If Peer Q receives any datagram (apparently) from Peer P that does not contain channel ID
chanQ, Peer Q MUST discard the datagram, but SHOULD continue to process datagrams from Peer P that do meet this requirement. Once Peer Q receives a datagram from Peer P that does contain channel IDchanQ, Peer Q knows that Peer P really received its reply datagram, and the three-way handshake and channel establishment is complete โ Peer Q MAY now also start sending messages with heavy payload to Peer P. Full handshake energy ๐ - If Peer P decides it no longer wants to communicate with Peer Q, or vice versa, the peer SHOULD send a closing
HANDSHAKEmessage to the other, as described above. Bye bye, bestie ๐ซถ
3.2. HAVE ๐
okay so the HAVE message is how a peer tells everyone which chunks it has ready to download ๐ฆ. The chunks can be described using different chunk addressing and availability map compression schemes, which are covered in Section 4 โจ. A HAVE message can give a full overview of a peer's chunk availability, or it can send updates to that set ๐ซถ.
Here's the big rule. Whenever a receiving Peer P has successfully checked the integrity of a chunk, or an interval of chunks, it MUST send a HAVE message to all peers Q1..Qn it wants to let download those chunks ๐ฅ. Peer P's policy decides when the HAVE goes out. Peer P can send it right away, or it can wait until it has other data to send to Peer Qi, or until it has received and checked multiple chunks โญ. How urgent it is to spread the news depends on the chunk picking policy (see Section 9.1), so that's what sets the timing ๐
. HAVE messages can also hitch a ride on other messages (piggybacked). Peers that never get a HAVE basically can't download the new chunks, so the HAVE message can act as a method of choking ๐.
The HAVE message MUST contain the chunk specification of the received and verified chunks ๐. A receiving peer MUST NOT send a HAVE message to peers whose handshake procedure is still incomplete (see Section 12.1) ๐ซ. A peer SHOULD NOT send a HAVE message to peers that already have the complete content, like in video-on-demand scenarios ๐.
3.3. DATA ๐
The DATA message is how chunks of content actually get transferred ๐ฆ. It MUST contain the chunk ID of the chunk and the chunk itself. A peer MAY send DATA messages for multiple chunks in the same datagram ๐. The DATA message MAY also contain extra info if the specific congestion control mechanism needs it. Right now PPSPP uses LEDBAT [RFC6817] for congestion control, and LEDBAT needs the current system time sent along with the DATA message, so the current system time MUST be included โฐโจ.
3.4. ACK ๐ซง
ACK messages MUST be sent to acknowledge received chunks if PPSPP is run over an unreliable transport protocol ๐จ. If a reliable transport protocol is used, ACK messages MAY be sent ๐. In the unreliable case, a receiving peer that has successfully checked the integrity of a chunk, or an interval of chunks C, MUST send an ACK message containing a chunk specification for C ๐ง. Since LEDBAT is in play, an ACK message MUST contain the one-way delay, computed from the peer's current system time that came in the DATA message โฑ๏ธ. A peer MAY delay sending ACK messages as defined in the LEDBAT specification [RFC6817] ๐ธ.
3.5. INTEGRITY ๐
The INTEGRITY message carries whatever info the receiver needs to verify a chunk's integrity ๐ต๏ธโโ๏ธ. Its payload depends on the content integrity protection scheme being used. When the Merkle Hash Tree scheme is used, an INTEGRITY message MUST contain a cryptographic hash of a subtree of the Merkle hash tree, plus the chunk specification that identifies that subtree ๐
.
Here's a typical example. When a peer wants to send a chunk and Merkle hash trees are in use, it builds a datagram with several INTEGRITY messages holding the hashes the receiver needs to verify the chunk, and then the actual chunk, encoded in a DATA message ๐. What the necessary hashes are, and the exact rules for encoding them into datagrams, are specified in Sections 5.3, and 5.4, respectively ๐งฉ.
3.6. SIGNED_INTEGRITY โ๏ธ
The SIGNED_INTEGRITY message carries digitally signed info the receiver needs to verify a chunk in live streaming ๐ก. Logically, it contains a chunk specification, a timestamp, and a digital signature ๐. Its exact payload depends on the live content integrity protection scheme in use, which is covered in Section 6.1 ๐.
3.7. REQUEST ๐โโ๏ธ
Bulk download protocols usually make explicit requests for certain ranges of data. That's the pull model, and BitTorrent [BITTORRENT] is a classic example ๐ฅ. Live streaming protocols often go with a push model instead, with no requests, to save round trips โก. PPSPP supports both models, so it's giving versatility ๐.
The REQUEST message is how a peer asks another peer for one or more chunks. A REQUEST message MUST contain the specification of the chunks the requester wants to download ๐ซถ. A peer that gets a REQUEST message MAY send out the requested chunks (using DATA messages) ๐จ. When Peer Q gets multiple REQUESTs from the same Peer P, Peer Q SHOULD process them in the order they were received ๐ข. Multiple REQUEST messages MAY be sent in one datagram, for example when a peer wants to request several rare chunks at once ๐.
With a live push model, a peer that gets REQUESTs also MAY send some other chunks in case it runs out of requests or for some other reason ๐. In that case, the only job of REQUEST messages is to give hints and help peers coordinate, so nobody retransmits data for no reason ๐ง .
3.8. CANCEL ๐
When downloading on-demand or live streaming content, a peer can ask multiple peers for urgent data to boost the odds it arrives on time ๐โโ๏ธ๐จ. Specifically, if the chunk picking algorithm (see Section 9.1) notices that a request for urgent data might not get served on time, the same request can be sent to a different peer ๐. When a Peer P decides to request urgent data from a Peer Q, Peer P SHOULD send a CANCEL message to all the peers the data was previously requested from ๐ซฃ. The CANCEL message has the specification of the chunks Peer P no longer wants ๐ฎ.
There's also an implicit cancel. When Peer Q receives a HAVE message for that urgent data from Peer P, Peer Q MUST also cancel the previous REQUEST(s) from Peer P ๐
. In other words, the HAVE message acts as an implicit CANCEL ๐ช.
3.9. CHOKE and UNCHOKE ๐ง
Peer A can send a CHOKE message to Peer B to say it won't be responding to REQUEST messages from Peer B anymore, for example because Peer A's upload capacity is maxed out ๐ฎโ๐จ. Peer A MAY later send an UNCHOKE message to say it'll answer new REQUESTs from Peer B again (Peer A SHOULD discard old requests) ๐. When Peer B gets a CHOKE message from Peer A, it MUST NOT send new REQUEST messages, and it can't count on answers to any outstanding ones, since the chunk transfer is choked ๐ฅฒ. If Peer B is choked but gets a HAVE message from Peer A, it does NOT get automatically unchoked, so it MUST NOT send any new REQUEST messages either ๐
โโ๏ธ. CHOKE and UNCHOKE are informational, because responding to REQUESTs is OPTIONAL (see Section 3.7) ๐ญ.
3.10. Peer Address Exchange ๐บ๏ธ
3.10.1. PEX_REQ and PEX_RES Messages ๐
Peer Exchange (PEX) messages are everywhere in peer-to-peer protocols ๐. They let peers swap the transport addresses of the peers they're currently talking to, which cuts down on how often they need to contact a central tracker (or a Distributed Hash Table) to discover new peers ๐. The strength here is that it enables decentralized peer discovery: after an initial bootstrap, you don't need a central tracker anymore ๐. The weakness is that it opens the door to a bunch of attacks, so it should not be used on the Internet unless extra security measures are in place ๐จ๐.
PPSPP supports peer-address exchange on the Internet and in benign private networks, as an OPTIONAL feature (not mandatory to implement), under certain conditions ๐ซถ. Here's the general mechanism. To get some peer addresses, a Peer A MAY send a PEX_REQ message to Peer B. Peer B MAY respond with one or more PEX_REScert messages. Logically, a PEX_REScert reply contains the address of a single peer Ci. Peer B MUST have exchanged messages with Peer Ci in the last 60 seconds to guarantee liveliness โณ. Once Peer A gets them, it may contact any, all, or none of the returned peers Ci ๐คทโโ๏ธ. Or, peers MAY ignore PEX_REQ and PEX_REScert messages if they're not interested in finding new peers, because of security considerations (rate limiting), or for any other reason ๐.
To use PEX in PPSPP on the Internet, two conditions must be met ๐ง:
- Peer transport addresses must be relatively stable.
- A peer must not obtain all its peer addresses through PEX.
The full security analysis for PEX messages is in Section 12.2 ๐. Physically, a PEX_REScert message carries a swarm-membership certificate rather than an IP address and port ๐. A membership certificate for Peer C says that Peer C at address (ipC,portC) is part of Swarm S at Time T, and it's cryptographically signed by an issuer โ๏ธ. The receiver, Peer A, can check the certificate for a valid signature from a trusted issuer, the right swarm, and liveliness, and only then consider contacting C โ
. These swarm-membership certificates line up with signed node descriptors in secure decentralized peer sampling services [SPS] ๐ง.
There are several possible designs for the security setup around these membership certificates ๐ ๏ธ. Different designs can decide who signs the membership certificates and how public keys get distributed. Section 12.2.2 describes an example where a central tracker acts as the Certification Authority ๐๏ธ.
In a hostile environment like the Internet, peers also have to make sure they don't end up talking only to malicious peers when they use peer-address exchange ๐ฑ. So peers MUST ensure that part of their connections are to peers whose addresses came from a trusted and secured tracker (see Section 12.2.3) ๐ก๏ธ.
Besides the PEX_REScert, there are two other PEX reply messages ๐ฏ. The PEX_RESv4 message contains a single IPv4 address and port. The PEX_RESv6 message contains a single IPv6 address and port. They MUST only be used in a benign environment, like a private network, because they give no guarantees that the host addressed actually takes part in a PPSPP swarm ๐
โโ๏ธ.
Once a PPSPP implementation has a list of peers (from PEX, from a central tracker, or from a Distributed Hash Table (DHT)), it has to figure out which peers to actually contact ๐งญ. This is where it can benefit from information provided by network or content providers, which helps improve network usage and boost PPSPP performance ๐. How a peer-to-peer (P2P) system like PPSPP can do these optimizations using the Application-Layer Traffic Optimization (ALTO) protocol is described in detail in [RFC7285], Section 7 ๐.
3.11. Channels ๐
okay so peers have a MAJOR problem: NATs and firewalls make it SO hard for them to talk to each other ๐ญ so PPSPP uses this multiplexing move called channels, which lets multiple swarms share the same transport address ๐ฆ channels are kinda like TCP connections, and each channel belongs to a single swarm (see Figure 1 โจ). just like TCP, every channel gets an identifier that's local to the peer at each end (think TCP port energy), and that identifier MUST be randomly chosen ๐ฒ so the two peers on one channel use different IDs to name the same channel, and that's the point! the IDs are different and random for security reasons, see Section 12.1 ๐ literally iconic.
In the PPSP-over-UDP encapsulation (Section 8.3), when a Channel C has been set up between Peer A and Peer B, the datagrams carrying messages from Peer A to Peer B get a four-byte channel ID prefix that Peer B picked, and it's the other way around for datagrams from Peer B to Peer A ๐ the channel IDs get swapped during the handshake procedure (see Section 8.4), and the handshake's starting datagram uses channel ID 0 ๐ง also, PPSPP can team up with Session Traversal Utilities for NAT (STUN) [RFC5389] ๐ซถ
_________ _________ _________
| | | | | |
| Swarm | | Swarm | | Swarm |
| Mgr | | A | | B |
|_______| |_______| |_______|
| | / \
| | / \
____|____ ____|____ ______/__ _\_______
| | | | | | | |
| Chan | | Chan | | Chan | | Chan |
| 0 | | 481 | | 836 | | 372 |
|_______| |_______| |_______| |_______|
| | | |
| | | |
____|____________|____________|____________|____
| |
| UDP |
| port 6778 |
|______________________________________________|
Figure 1 is a peer's network stack, and it's giving organized ๐ this peer is reachable on UDP port 6778 and talks over channel 481 to one peer in Swarm A, and over channels 836 and 372 to two peers in Swarm B. channel ID 0 is the special one, reserved for handshaking ๐ซถ
3.12. Keep Alive Signaling ๐
okay this part is about staying in touch with the peers you care about ๐ฅน a peer SHOULD send a "keep alive" message periodically to each peer it's interested in, as long as it has no other messages to send them right now ๐ซถ the whole goal is to keep a signaling channel open to the peers who matter. which peers count as "of interest" is decided by a policy about who's useful now and soon. this document doesn't prescribe a policy, but some examples are (a) peers that have chunks on offer that this client needs, or (b) peers that don't have interesting chunks on offer right now (because they're still downloading themselves, or doing live streaming) but gave good performance in the past ๐ when those peers get new chunks, the peer that kept the channel open can use them again โจ periodic keep alives also stop other peers from declaring you dead, which is a whole mood ๐
A guideline for declaring a peer dead when using UDP is a three minute delay since the last packet was received from that peer, plus at least three datagrams sent to that peer during the same period ๐ญ when a peer is declared dead, the channel to it closes, no more messages get sent to that peer, and the local administration about it is discarded ๐ busy servers can also force idle clients to disconnect by just not sending keep alives. PPSPP doesn't define an explicit message type for "keep alive" messages. in the PPSP-over-UDP encapsulation, they're simply datagrams made of a four-byte channel ID only (see Sections 8.3 and 8.4) ๐
4. Chunk Addressing Schemes ๐
okay now we're talking about how chunks get addressed ๐ PPSPP can use different methods of chunk addressing, meaning it can support different ways of identifying chunks and different ways of squishing a peer's chunk availability map into something compact ๐ง
All peers in a swarm MUST use the same chunk addressing method. no mixing and matching, bestie ๐ โโ๏ธ
4.1. Start-End Ranges ๐ธ
a chunk specification is a single (start specification, end specification) pair that identifies a range of chunks, with the end inclusive โจ both the start and end specifications can use one of several addressing schemes. two schemes are defined right now: chunk ranges and byte ranges ๐
4.1.1. Chunk Ranges
here, the start and end specifications are both chunk identifiers. chunk identifiers are 32-bit or 64-bit unsigned integers ๐ข a PPSPP peer MUST support this scheme, so it's the non-negotiable one ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~ Start chunk (32 or 64) ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~ End chunk (32 or 64) ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4.1.2. Byte Ranges
this time the start and end specifications are 64-bit byte offsets in the content ๐ฆ support for this scheme is OPTIONAL, so it's a bonus, not a must ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Start byte offset (64) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| End byte offset (64) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4.2. Bin Numbers ๐
PPSPP brings a novel way of addressing chunks of content called "bin numbers" (or "bins" for short) ๐ง bin numbers let you address a binary interval of data with a single integer. this cuts down the state that has to be recorded per peer and the space needed to denote intervals on the wire, so the protocol stays lightweight ๐ชถ basically, this numbering system lets PPSPP work with simpler data structures, like arrays instead of binary trees, which means less complexity overall ๐ the support for this scheme is OPTIONAL.
in bin addressing, the smallest binary interval is a single chunk (for example a block of bytes that may be of variable size), and the largest interval is a complete range of 2**63 chunks ๐ the novel part is that these intervals get numbered so they lay out into a vector nicely, and that's called bin numbering. here's how it works. take a chunk interval of width W. to derive the bin numbers of the complete interval and its subintervals, you build a minimal balanced binary tree that's at least W chunks wide at the base. the leaves, read left to right, correspond to chunks 0..W-1 in the interval, and each leaf has bin number I*2, where I is the index of the chunk (counting beyond W-1 to balance the tree) โจ the bin number of a higher-level node P in the tree is calculated like this:
binP = (binL + binR) / 2
where binL is the bin of node P's left-hand child and binR is the bin of node P's right-hand child ๐ since every node in the tree stands for a subinterval of the original interval, each subinterval can be addressed by one single integer. so cute ๐ฅน here's the bin number tree for an interval of width W=8:
7
/ \
/ \
/ \
/ \
3 11
/ \ / \
/ \ / \
/ \ / \
1 5 9 13
/ \ / \ / \ / \
0 2 4 6 8 10 12 14
C0 C1 C2 C3 C4 C5 C6 C7
The bin number tree of an interval of width W=8
Figure 2 is that tree, and it's the blueprint for everything bin-related ๐ bin 7 covers the complete interval, bin 3 covers chunks C0..C3, bin 1 covers chunks C0 and C1, and bin 2 is just chunk C1 ๐ง the special numbers 0xFFFFFFFF (32-bit) or 0xFFFFFFFFFFFFFFFF (64-bit) mean an empty interval, and 0x7FFF...FFF means "everything" ๐
when bin numbering is in use, a chunk's ID is its corresponding (leaf) bin number in the tree, and the chunk specification in HAVE and ACK messages is just a single bin number (32-bit or 64-bit), like this:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~ Bin number (32 or 64) ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4.3. In Messages ๐
4.3.1. In HAVE Messages
when a receiving peer has successfully checked the integrity of a chunk or interval of chunks, it MUST send a HAVE message to all the peers it wants to let download those chunks from ๐ฅ being able to hold back HAVE messages is what makes them usable as a method of choking, which is kinda genius ๐ค the HAVE message MUST contain the chunk specification of the biggest complete interval of all chunks the receiver has received and checked so far that fully includes the interval just received ๐งฉ so the chunk specification MUST denote at least the interval received, but the receiver is supposed to aggregate and acknowledge bigger intervals when possible ๐ซถ
as a result, every single chunk gets acknowledged a logarithmic number of times ๐ธ that gives some necessary redundancy in acknowledgements, which sufficiently compensates for unreliable transport protocols ๐
Implementation note:
To record which chunks a peer has in the state that an implementation keeps for each peer, an implementation MAY use the efficient "binmap" data structure, which is a hybrid of a bitmap and a binary tree, discussed in detail in [BINMAP].
4.3.2. In ACK Messages
PPSPP peers MUST use ACK messages to acknowledge received chunks if an unreliable transport protocol is used ๐ when a receiving peer has successfully checked the integrity of a chunk or interval of chunks C, it MUST send an ACK message to the sending peer, containing the chunk specification of its biggest, complete interval covering C (see HAVE) ๐ฆ this is the same vibe as HAVE, just for acknowledging what you received ๐
5. Content Integrity Protection โจ
okay so PPSPP can protect the integrity of content while it's zipping around the peer-to-peer network ๐ซถ basically it gives receiving peers ways to check whether a chunk was maliciously changed by the peer that sent it. in benign environments, content integrity protection can be disabled ๐ธ
For static content, PPSPP currently defines just one method for protecting integrity, called the Merkle Hash Tree scheme. If PPSPP operates over the Internet, this scheme MUST be used. If PPSPP operates in a benign environment, this scheme MAY be used. so it's mandatory to implement, which is what you need for strong security in an IETF protocol [RFC3365] ๐ An extended version of the scheme protects dynamically generated content (live streams), as explained below and in Section 6.1.
The Merkle Hash Tree scheme works with different chunk addressing schemes. all it needs is the ability to address a range of chunks. In the description below, abstract node IDs are used to identify nodes in the tree. On the wire, those get translated into the matching range of chunks in whatever chunk addressing scheme you picked ๐ง
5.1. Merkle Hash Tree Scheme
PPSPP names content using self-certification, and it's honestly so clean ๐ญ content is identified by a single cryptographic hash, the root hash of a Merkle hash tree calculated recursively from the content [ABMRKL]. this self-certifying hash tree lets every peer directly detect when a malicious peer tries to distribute fake content. it also means only a small amount of information is needed to start a download (the root hash and some peer addresses) ๐ For live streaming, a dynamic tree and a public key are used, see below.
okay so here's how the tree gets built for a content file split into N chunks. the construction doesn't assume chunks have to be a fixed size, which is a big vibe ๐ฆ Given a cryptographic hash function, more specifically an MDC [HAC01], such as SHA-256, you calculate the hashes of all the chunks. Next, you create a binary tree of sufficient height. Sufficient height means the lowest level in the tree has enough nodes to hold all chunk hashes in the set, as with bin numbering. The figure below shows the tree for a content file consisting of 7 chunks. Just like the content addressing scheme, the leaves of the tree correspond to a chunk and, in this case, are assigned the hash of that chunk, starting at the leftmost leaf. Since the base of the tree may be wider than the number of chunks, any remaining leaves are assigned an empty hash value of all zeros. Finally, the hash values of the higher levels get calculated by concatenating the hash values of the two children (again left to right) and computing the hash of that aggregate. If the two children are empty hashes, the parent is an empty all-zeros hash as well (to save computation) ๐งฎ This process ends in a hash value for the root node, which is called the "root hash". Note the root hash only depends on the content, so any modification of the content will result in a different root hash ๐ฅ
7 = root hash
/ \
/ \
/ \
/ \
3* 11
/ \ / \
/ \ / \
/ \ / \
1 5 9 13* = uncle hash
/ \ / \ / \ / \
0 2 4 6 8 10* 12 14
C0 C1 C2 C3 C4 C5 C6 E
=chunk index ^^ = empty hash
Merkle hash tree of a content file with N=7 chunks
Figure 3
5.2. Content Integrity Verification
say a peer gets the root hash of the content it wants from a trusted source ๐ซถ it can then check the integrity of any chunk it receives like this. First, it calculates the hash of the chunk it received, for example, chunk C4 in the previous figure. Along with this chunk, it MUST receive the hashes required to check that chunk's integrity. In principle, these are the hash of the chunk's sibling (C5) and those of its "uncles". A chunk's uncles are the sibling Y of its parent X, and the uncle of that Y, recursively until the root is reached. For chunk C4, the uncles are nodes 13 and 3 and the sibling is 10; all of them are marked with a * in the figure ๐ Using this information, the peer recalculates the root hash of the tree and compares it to the root hash it got from the trusted source. If they match, the chunk has been positively verified as the requested part of the content โจ Otherwise, the sending peer sent either the wrong content or the wrong sibling or uncle hashes. For simplicity, the set of sibling and uncle hashes is collectively referred to as the "uncle hashes".
In the case of live streaming, the tree of chunks grows dynamically, and the root hash is undefined or, more precisely, transient, for as long as new data is being generated by the live source ๐ Section 6.1.2 defines a method for content integrity verification for live streams that works with such a dynamic tree. Even though the tree is dynamic, content verification works the same for both live and predefined content, so you get one unified method for both types of streaming ๐
5.3. The Atomic Datagram Principle
as explained above, a datagram is a sequence of messages. Ideally, every datagram sent is independent of the others: each datagram SHOULD be processed separately, and losing one datagram must not disrupt the flow of datagrams between two peers ๐ง Since a datagram carries zero or more messages, both messages and message interdependencies SHOULD NOT span multiple datagrams.
this principle means that as any chunk is verified using its uncle hashes, the necessary hashes SHOULD be put into the same datagram as the chunk's data. If that's not possible because of a limitation on datagram size, the necessary hashes MUST be sent first in one or more datagrams. As a general rule, if some additional data is still missing to process a message within a datagram, the message SHOULD be dropped ๐
The hashes needed to verify a chunk are, in principle, its sibling's hash and all its uncle hashes, but the set of hashes to send can be optimized ๐ Before sending a packet of data to the receiver, the sender inspects the receiver's previous acknowledgements (HAVE or ACK) to work out which hashes the receiver already has for sure. Suppose the receiver had acknowledged chunks C0 and C1 (the first two chunks of the file). Then it must already have uncle hashes 5, 11, and so on, because those hashes are necessary to check C0 and C1 against the root hash. Then, hashes 3, 7, and so on must also be known, since they're calculated in the process of checking the uncle hash chain. So, to send chunk C7, the sender only needs to include the hashes for nodes 14 and 9, which let the data be checked against hash 11, which the receiver already knows ๐
The sender MAY optimistically skip hashes that were sent out in previous, still-unacknowledged datagrams. It's an optimization trade-off between redundant hash transmission and the risk of collateral data loss. That risk applies when some necessary hashes are lost in the network, so some delivered data can't be verified and thus has to be dropped ๐ญ Either way, the receiver builds the Merkle hash tree on-demand, incrementally, starting from the root hash, and uses it for data validation.
In short, the sender MUST put into the datagram the hashes it believes are necessary for the receiver to verify the chunk ๐ซถ The receiver MUST remember all the hashes it needs to verify missing chunks that it still wants to download. Note that the latter implies a hardware-limited receiver MAY forget some hashes if it does not plan to announce possession of those chunks to others (i.e., does not plan to send HAVE messages.) ๐
5.4. INTEGRITY Messages
Concretely, a peer that wants to send a chunk of content creates a datagram that MUST consist of a list of INTEGRITY messages followed by a DATA message ๐ If the INTEGRITY messages and the DATA message can't fit into a single datagram because of a limitation on datagram size, the INTEGRITY messages MUST be sent first in one or more datagrams. The list of INTEGRITY messages sent MUST contain an INTEGRITY message for each hash the receiver misses for integrity checking. An INTEGRITY message for a hash MUST contain the chunk specification corresponding to the node ID of the hash, plus the hash data itself. The chunk specification for a node ID is the range of chunks formed by the leaves of the subtree rooted at that node. For example, node 3 in Figure 3 denotes chunks 0, 2, 4, and 6, so the chunk specification should denote that interval ๐ง The list of INTEGRITY messages MUST be sorted in order of the tree height of the nodes, descending (the leaves are at height 0). The DATA message MUST contain the chunk specification of the chunk and the chunk itself. A peer MAY send the required messages for multiple chunks in the same datagram, depending on the encapsulation ๐
5.5. Discussion and Overhead ๐
okay so the way BitTorrent [BITTORRENT] handles content integrity is literally not built for streaming ๐ญ it hands clients the hashes of every chunk up front, through metadata files (called .torrent files in BitTorrent). but when chunks are tiny, like in the current UDP encapsulation of PPSPP, that means downloading a huge pile of hashes before any content can even start. so time-till-playback goes way up for end users, and that's a major vibe killer for streaming ๐
the overhead of Merkle hash trees is actually lowkey pretty chill ๐ธ the size of the tree (counted as total nodes) depends on how many chunks the content gets divided into, which means it depends on the chunk size too. the formula is:
nnodes = math.pow(2,math.log(nchunks,2)+1)
in principle, a peer needs the hash values of all these nodes sent to it once so it can verify every chunk. so the worst-case on-the-wire overhead is hashsize * nnodes. but the real number of hashes sent can be optimized, as described in Section 5.3 โจ
now, to see how a peer can verify all the chunks without getting every hash, check out the example tree from Section 5.1 ๐ฆ in a simple progressive download of chunks 0, 2, 4, 6, and so on, the sending peer sends these hashes:
+-------+---------------------------------------------+
| Chunk | Node IDs of hashes sent |
+-------+---------------------------------------------+
| 0 | 2,5,11 |
| 2 | - (receiver already knows all) |
| 4 | 6 |
| 6 | - |
| 8 | 10,13 (hash 3 can be calculated from 0,2,5) |
| 10 | - |
| 12 | 14 |
| 14 | - |
| Total | # hashes 7 |
+-------+---------------------------------------------+
Table 1: Overhead for the Example Tree
so the total number of hashes sent (7) is less than the total number of hashes in the tree (16) ๐ that's because a peer doesn't need hashes that were already calculated and verified as part of earlier chunks. main character efficiency, honestly ๐
5.6. Automatic Detection of Content Size ๐ง
okay this one is so cute ๐ฅน in PPSPP, the size of a static content file (think a video file) can be reliably and automatically worked out from info received from the network, as long as fixed-size chunks are used. so you don't have to put the file size in the content's metadata for these files. implementations of PPSPP MAY use this automatic detection feature ๐ and fyi, this feature is the only thing in PPSPP that requires a fixed-size chunk. it's built on the Merkle hash tree and the trusted root hash (the swarm ID), like this:
5.6.1. Peak Hashes ๐ฅ
the newcomer peer's ability to detect the content size really depends on peak hashes ๐ and peak hashes depend on the ideas of filled and incomplete nodes. remember when we built the binary trees for content verification? the base of the tree can have more leaves than the content has chunks. those extra leaves got empty all-zero hashes so the higher-level hashes could still be calculated ๐ so now a filled node is one whose interval of leaves contains only hashes of content chunks, no empty hashes. an incomplete (not filled) node covers an interval that also has empty hashes, usually one that runs past the end of the file. in the figure below, nodes 7, 11, 13, and 14 are incomplete, and everything else is filled โจ
formally, a peak hash is the hash of a filled node whose sibling is an incomplete node. practically, say a file is 7162 bytes long and a chunk is 1 kilobyte. that file fits into 7 chunks, and the tail chunk is 1018 bytes long. the Merkle hash tree for that file is shown in Figure 4. following the definition, the peak hashes of this file are in nodes 3, 9, and 12, marked with an *. E marks an empty hash ๐ฆ
7
/ \
/ \
/ \
/ \
3* 11
/ \ / \
/ \ / \
/ \ / \
1 5 9* 13
/ \ / \ / \ / \
0 2 4 6 8 10 12* 14
C0 C1 C2 C3 C4 C5 C6 E
= 1018 bytes
Peak hashes in a Merkle hash tree
Figure 4
peak hashes can be explained through the binary representation of the number of chunks the file takes up ๐ง the binary representation of 7 is 111. every "1" in the binary representation of the file's packet length corresponds to a peak hash. for this file there are indeed three peaks: nodes 3, 9, and 12. so the number of peak hashes for a file is also, at most, logarithmic in its size ๐
a peer that knows which nodes hold the peak hashes can work out how many chunks the file has, which gives an estimate of the file size (since all chunks but the last are a fixed size) ๐ซถ the peak node IDs can be securely shared from one (untrusted) peer, Peer A, to another peer, Peer B, by having Peer A send the peak hashes and their node IDs to Peer B. it can be shown that the root hash Peer B got from a trusted source is enough to verify these are the right peak hashes, and here's why ๐
Lemma: Peak hashes can be checked against the root hash.
Proof: (a) Any peak hash is always the left sibling. Otherwise, if it is the right sibling, its left neighbor/sibling must also be a filled node, because of the way chunks are laid out in the leaves, which contradicts the definition of a peak hash. (b) For the rightmost peak hash, its right sibling is zero. (c) For any peak hash, the right sibling might be calculated using peak hashes to the left and zeros for empty nodes. (d) Once the right sibling of the leftmost peak hash is calculated, its parent might be calculated. (e) Once that parent is calculated, we might trivially get to the root hash by concatenating the hash with zeros and hashing it repeatedly.
informally, the Lemma basically says: peak hashes cover all the data, so the remaining hashes are either trivial (zeros) or can be calculated from peak hashes and zero hashes ๐
finally, once Peer B has the number of chunks in the content, it can work out the exact file size ๐ญ since all chunks except the last are a fixed size, Peer B only needs to know the size of the last chunk. knowing the number of chunks, Peer B can calculate the node ID of the last chunk and download it. as always, Peer B verifies this chunk's integrity against the trusted root hash. since only one chunk of data leads to a successful verification, the size of that chunk has to be correct. then Peer B can determine the exact file size as:
(number of chunks -1) * fixed chunk size + size of last chunk
5.6.2. Procedure ๐
a PPSPP implementation that wants to use automatic size detection MUST operate as follows. when Peer A sends a DATA message to Peer B for the first time, Peer A MUST first send all the peak hashes for the content, in INTEGRITY messages, unless Peer B has already signaled that it knows the peak hashes by having acknowledged any chunk. if they're needed, the peak hashes MUST be sent as an extra list of uncle hashes for the chunk, before the list of actual uncle hashes of the chunk as described in Section 5.3. the receiver, Peer B, MUST check the peak hashes against the root hash to determine the approximate content size. to get the definite content size, Peer B MUST download the last chunk of the content from any peer that offers it ๐ฅ
let's run through an example ๐ง say a 7162-byte file that fits in 7 chunks of 1 kilobyte is distributed by Peer A. Figure 4 shows the relevant Merkle hash tree. Peer B only knows the root hash of the file after successfully connecting to Peer A, and it requests the first chunk of data, C0 in Figure 4. Peer A replies by putting these messages in the datagram, in this exact order: first, the three peak hashes of this file, the hashes of nodes 3, 9, and 12; second, the uncle hashes of C0; and then the DATA message with the actual content of C0 โจ when Peer B gets the peak hashes, it checks them against the root hash and figures out the file is 7 chunks long. to pin down the exact size, Peer B needs to request and retrieve the last chunk containing data, C6 in Figure 4. once that last chunk is retrieved and verified, Peer B concludes it's 1018 bytes long, which means the file is exactly 7162 bytes long ๐
6. Live Streaming ๐ธ
the messages defined above can be used for live streaming too ๐ in a pull-based model, a live streaming injector can announce the chunks it generates via HAVE messages, and peers can retrieve them via REQUEST messages. the areas that need special attention are content authentication and chunk addressing (to get an infinite stream of chunks) ๐ฆ
6.1. Content Authentication โจ
okay so for live streaming, PPSPP gives you two ways for a peer to check that the content it gets from another peer is legit: "Sign All" and "Unified Merkle Tree" ๐๐
In the "Sign All" method, the live injector signs every single chunk with a private key. When a peer gets a chunk, it checks the signature against the matching public key, which it got from a trusted source. Supporting this method is OPTIONAL ๐ธ
In the "Unified Merkle Tree" method, PPSPP mixes the Merkle Hash Tree scheme for static content with signatures, so video-on-demand and live streaming end up working the same way ๐ฆ The Merkle hash trees mean fewer signing and verification operations, which gives you similar signature amortization to what's described in [SIGMCAST]. If PPSPP runs over the Internet, the "Unified Merkle Tree" method MUST be used. If the protocol runs in a benign environment, the "Unified Merkle Tree" method MAY be used. So yes, this method is mandatory to implement, bestie ๐
In both methods, the swarm ID is a public key, encoded the way a DNSSEC DNSKEY resource record is encoded, minus the Base64 encoding [RFC4034] ๐ง
Specifically, the swarm ID is a 1-byte Algorithm field, which says what cryptographic algorithm the public key uses and decides the format of the Public Key field that comes after it. The value of that Algorithm field has to be one of the values in the "Domain Name System Security (DNSSEC) Algorithm Numbers" registry [IANADNSSECALGNUM]. The RSASHA1 [RFC4034], RSASHA256 [RFC5702], ECDSAP256SHA256 and ECDSAP384SHA384 [RFC6605] algorithms are mandatory to implement ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Algo Number(8)| ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~ DNSSEC Public Key (variable) ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
6.1.1. Sign All ๐
In the "Sign All" method, the live injector signs every chunk with a private key, and peers, once they've got a chunk, check the signature using the matching public key from a trusted source. The vibe here is that, in PPSPP, the swarm ID of the live stream is that public key ๐
A peer that wants to send a chunk builds a datagram that MUST contain a SIGNED_INTEGRITY message with the chunk's signature, followed by a DATA message holding the actual chunk. If the SIGNED_INTEGRITY message and the DATA message can't fit in a single datagram because of the datagram size limit, the SIGNED_INTEGRITY message MUST be sent first, in its own separate datagram ๐ฆ The SIGNED_INTEGRITY message is made up of the chunk specification, the timestamp, and the digital signature โจ
The digital signature algorithm is picked by the Live Signature Algorithm protocol option, see Section 7.7. The signature is computed over a concatenation of the on-the-wire representation of the chunk specification, a 64-bit timestamp in NTP Timestamp format [RFC5905], and the chunk, in that order. The timestamp is the time signature that was made at the injector in UTC ๐ฐ๏ธ
6.1.2. Unified Merkle Tree ๐
In this method, the chunks are used as the basis for a Merkle hash tree, same as for static content. The twist is that chunks keep getting generated, so the tree isn't static, it's dynamic ๐ That means the tree doesn't have a root hash, or, to be more precise, it has a transient root hash. So a public key ends up serving as the swarm ID of the content. The key is used to digitally sign updates to the tree, which lets peers grow it based on trusted information using the process below ๐ซถ
6.1.2.1. Signed Munro Hashes
The live injector generates a number of chunks, called NCHUNKS_PER_SIG, which is a fixed power of 2 (NCHUNKS_PER_SIG>=2). These chunks get added as new leaves to the existing hash tree. Because of that expansion, the tree gets a new subtree that is NCHUNKS_PER_SIG chunks wide at the base. The root of that new subtree is called the munro of the subtree, and its hash is the munro hash of the subtree. Check out Figure 5 ๐ฆ Here, node 5 is the new munro, marked with a $ sign.
3
/ \
/ \
/ \
1 5$
/ \ / \
0 2 4 6
Expanded live tree. With NCHUNKS_PER_SIG=2, node 5 is the munro for
the new subtree spanning 4 and 6. Node 1 is the munro for the
subtree spanning chunks 0 and 2, created in the previous iteration.
Figure 5
Informally, here's how it goes ๐ The injector signs only the munro hash of the new subtree with its private key. Then it tells its peers the new subtree exists, using HAVE messages. When a peer answers those HAVE messages by requesting a chunk from the new subtree, the injector first sends the signed munro hash for the requested chunk. After that, a lot like static content, the injector sends the uncle hashes needed to verify that chunk, as in Section 5.1. The difference is that the uncle hashes are used to verify the requested chunk against the munro hash, not the root hash like with static content. Last of all, the injector sends the actual chunk ๐
The receiving peer checks the signature on the signed munro using the swarm ID (a public key), then updates its hash tree. Once the peer knows the munro hash is trusted, it can verify every chunk in the subtree against that munro hash, using the accompanying uncle hashes as in Section 5.1 ๐ญโจ
Let's run through the next round so it clicks. The injector has the tree from Figure 5, and it's connected to a bunch of peers who have that same tree and all have chunks 0, 2, 4, and 6. When the injector generates two new chunks (NCHUNKS_PER_SIG=2), the tree grows the way Figure 6 shows. The two new chunks, 8 and 10, extend the tree on the right, and to fit them a new root is made: node 7. Since this tree is wider at the base than the number of chunks, there are two empty leaves right now. The munro node for the new subtree is 9, marked with a $ sign ๐ง
7
/ \
/ \
/ \
/ \
3 11
/ \ / \
/ \ / \
/ \ / \
1 5 9$ 13
/ \ / \ / \ / \
0 2 4 6 8 10 E E
Expanded live tree. With NCHUNKS_PER_SIG=2, node 9 is the munro of
the newly added subtree spanning chunks 8 and 10.
Figure 6
Now the injector has to tell its peers about the updated tree, including the new munro hash 9. So it sends a HAVE message with a chunk specification for nodes 8 + 10 to its peers. Peer P responds by asking for the new chunk, say chunk 8, from the injector with a REQUEST message ๐ In reply, the injector sends the signed munro hash of node 9 as an INTEGRITY message carrying the hash of node 9, plus a SIGNED_INTEGRITY message carrying the signature of the hash of node 9. Those are followed by an INTEGRITY message with the hash of node 10 and a DATA message with chunk 8 ๐
When Peer P gets all that, it verifies the signature on the munro and expands its view of the tree. Then the peer computes the hash of chunk 8 and combines it with the received hash of node 10, which gives the expected hash of node 9. Next, it checks chunk 8 by comparing the computed hash of node 9 with the munro hash of that same node it just received. Boom, Peer P has successfully verified the integrity of chunk 8 ๐ฅ
This procedure needs just one signing operation for every NCHUNKS_PER_SIG chunks created, and one verification operation for every NCHUNKS_PER_SIG received, which makes it way cheaper than "Sign All" ๐ A receiving peer does also need to check one or more hashes per chunk through the Merkle Hash Tree scheme, but that needs fewer hardware resources than a signature verification for every chunk. The approach is similar to signature amortization through Merkle Tree Chaining [SIGMCAST]. The catch is higher latency. A peer can't download the new chunks until the injector has computed the signature and announced the subtree. A peer MUST check the signature before forwarding the chunks to other peers [POLLIVE] ๐ซ
The number of chunks per signature, NCHUNKS_PER_SIG, MUST be a fixed power of 2 for simplicity. NCHUNKS_PER_SIG MUST be larger than 1 for performance reasons. Two things matter when picking a value. First, the CPU load clients can afford for signature verifications, given the stream's bitrate. To keep CPU load low on a high bitrate stream, NCHUNKS_PER_SIG should be high. Second, the effect on latency, which goes up as NCHUNKS_PER_SIG gets higher, as we just talked about. Also note that the procedure doesn't rule out variable-size chunks ๐ธ
This integrity method has an extra perk ๐ If some peers in the system saved the whole broadcast, then the moment the broadcast ends, the content is available as a video-on-demand download, using the now stabilized tree and the final root hash as the swarm identifier. Peers that saved all the chunks can announce the root hash to the tracking infrastructure and instantly seed the content ๐
6.1.2.2. Munro Signature Calculation โจ
The digital signature algorithm is determined by the Live Signature Algorithm protocol option, see Section 7.7. The signature is computed over a concatenation of the on-the-wire representation of the chunk specification of the munro node (see Section 6.1.2.1), a timestamp in 64-bit NTP Timestamp format [RFC5905], and the hash associated with the munro node, in that order. The timestamp is the time signature that was made at the injector in UTC โญ
6.1.2.3. Procedure ๐
okay so bestie, the injector MUST NOT send a HAVE message for chunks in the new subtree until it has computed the signed munro hash for that subtree ๐ฅ no shortcuts, no cap!!
when Peer B asks Peer A for a chunk C (whether Peer A is the injector or just some other peer), and Peer A decides to reply, it has to follow these steps ๐ . First, Peer A MUST send an INTEGRITY message with the chunk specification for the munro of chunk C plus the munro's hash ๐ฆ. Then it MUST send a SIGNED_INTEGRITY message with the chunk specification for the munro, the timestamp, and its signature, all bundled into a single datagram โจ. The only time this gets skipped is if Peer B already said it has a chunk with the same corresponding munro earlier in the exchange, using HAVE or ACK messages ๐.
After those two messages (if there are any), Peer A MUST send the missing uncles hashes it needs to verify the chunk against its munro hash, and then the chunk itself, as described in Section 5.4 ๐ธ. Sharing datagrams is encouraged whenever it's possible ๐ซถ.
6.1.2.4. Secure Tune In ๐ง
okay so when a peer tunes in to a live stream, it has to figure out what the last chunk the injector made is ๐. To make this easier in the Unified Merkle Tree scheme, every peer shares what it knows about the injector's chunks by swapping their latest signed munro hashes ๐. Here's how it goes ๐ญ.
Quick recap from PPSPP: when Peer A starts a channel with Peer B, Peer A sends a first datagram with a HANDSHAKE message, and Peer B answers with a second datagram that also has a HANDSHAKE message (see Section 3.1) ๐. When Peer A sends a third datagram to Peer B and Peer B gets it, both peers know the other is listening on its stated transport address ๐. Peer B is then allowed to send heavy payload like DATA messages in the fourth datagram ๐. Peer A can already safely do that in the third datagram โจ.
In the Unified Merkle Tree scheme, Peer A MUST send its rightmost signed munro hash to Peer B in the third datagram, and in every datagram after that to Peer B, until Peer B indicates it has a chunk with the same corresponding munro or a more recent munro (by means of a HAVE or ACK message) ๐ฆ. Peer B might already have said this through HAVE messages in the second datagram ๐. Going the other way, when Peer B sends the fourth datagram or any later datagram to Peer A, Peer B MUST send its rightmost signed munro hash, unless Peer A already indicated it knows that munro or a more recent one ๐ . The rightmost signed munro hash of a peer is defined as the munro hash signed by the injector of the rightmost subtree of width NCHUNKS_PER_SIG chunks in the peer's Merkle hash tree ๐ฅ. Peer A MUST NOT send the signed munro hash in the first datagram of the HANDSHAKE procedure, and Peer B MUST NOT send it in the second datagram, because it counts as heavy payload ๐ญ๐.
When a peer gets a SIGNED_INTEGRITY message with a signed munro hash but the timestamp is too old, the peer MUST discard the message ๐ โโ๏ธ. Otherwise, it SHOULD use the signed munro to update its hash tree and pick a tune-in point in the live stream ๐. A peer may combine information from multiple peers to choose that tune-in point ๐ซถ.
6.2. Forgetting Chunks ๐
okay so as a live broadcast goes on, a peer might want to toss the chunks it already played out ๐ง. Ideally, other peers should know this so they don't keep asking that peer for those chunks ๐. This can happen when viewers pause a live stream, or when they start late, like joining a broadcast at 20:35 when it actually began at 20:30 ๐.
PPSPP has a simple fix so peers can keep up with what a discarding peer has available ๐ธ. A discarding peer in a live stream MUST enable the Live Discard Window protocol option, which says how many chunks/bytes it caches before the last chunk/byte it advertised as available (see Section 7.9) โจ. Its peers SHOULD apply this number as a sliding window filter over the peer's chunk availability, as shared through its HAVE messages ๐.
Three things matter when picking a value for this option: the playback buffer peers want, the bitrate of the stream, and the resources the peer has available ๐ . Think about a fresh peer joining the stream ๐ฆ. The size of the discard window on the peers it connects to decides how much it can download straight away to build its prebuffer ๐ฅ. If the window is smaller than the buffer it wants, the fresh peer has to wait until the peers have downloaded more of the stream before it can start playback ๐ญ. Media buffers are usually measured in seconds, so the discard window size also depends on the (average) bitrate of the stream ๐. Finally, if a peer has few resources for storing chunks and metadata, it SHOULD choose a small discard window ๐ซถ.
- Protocol Options ๐
okay so the HANDSHAKE message in PPSPP can carry the following protocol options โจ. Unless stated otherwise, a protocol option is an 8-bit code followed by an 8-bit value ๐. Larger values are all encoded big-endian ๐ . Each protocol option gets explained in the subsections below ๐ฆ. The list of protocol options MUST be sorted on code value (ascending) in a HANDSHAKE message ๐.
+--------+-------------------------------------+
| Code | Description |
+--------+-------------------------------------+
| 0 | Version |
| 1 | Minimum Version |
| 2 | Swarm Identifier |
| 3 | Content Integrity Protection Method |
| 4 | Merkle Hash Tree Function |
| 5 | Live Signature Algorithm |
| 6 | Chunk Addressing Method |
| 7 | Live Discard Window |
| 8 | Supported Messages |
| 9 | Chunk Size |
| 10-254 | Unassigned |
| 255 | End Option |
+--------+-------------------------------------+
Table 2: PPSPP Options
7.1. End Option ๐
okay so a peer MUST end the list of protocol options with the end option ๐. Anything after that should be treated as protocol messages ๐ธ. The code for the end option is 255, and unlike the others it has no value octet, so the option is just 1 octet long ๐ซถ.
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|1 1 1 1 1 1 1 1|
+-+-+-+-+-+-+-+-+
7.2. Version โจ
okay so a peer MUST include the maximum PPSPP version it supports as the first protocol option in the list ๐. The code for this option is 0 ๐ . Defined values are in Table 3 ๐ฆ.
+---------+----------------------------------------+
| Version | Description |
+---------+----------------------------------------+
| 0 | Reserved |
| 1 | Protocol as described in this document |
| 2-255 | Unassigned |
+---------+----------------------------------------+
Table 3: PPSPP Version Numbers
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0| Version (8) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
7.3. Minimum Version ๐
okay so when a peer starts the handshake, it MUST include the minimum PPSPP version it supports in the list of protocol options ๐ฅ. This follows the min/max versioning scheme from [RFC6709], Section 4.1, strategy 5 ๐. The code for this option is 1 ๐. Defined values are in Table 3 ๐ง.
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 1| Min. Ver. (8) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
7.4. Swarm Identifier ๐ฆ
okay so when a peer starts the handshake, it MUST include exactly one swarm identifier option ๐. If the peer is not the initiator, it MAY include a swarm identifier option as an end-to-end check ๐ซถ. This option looks like this ๐:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 1 0| Swarm ID Length (16) | ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~ Swarm Identifier (variable) ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The Swarm ID Length field holds the length of the single Swarm Identifier that follows, in bytes ๐ง. The Length field is 16 bits wide so it can handle big public keys as identifiers in live streaming ๐ธ.
Each PPSPP peer knows the IDs of the swarms it joins, so this info can be checked the moment it arrives โจ.
7.5. Content Integrity Protection Method ๐
okay so a peer MUST include the content integrity method used by a swarm ๐ฅ. The code for this option is 3 ๐. Defined values are in Table 4 ๐.
+--------+-------------------------+
| Method | Description |
+--------+-------------------------+
| 0 | No integrity protection |
| 1 | Merkle Hash Tree |
| 2 | Sign All |
| 3 | Unified Merkle Tree |
| 4-255 | Unassigned |
+--------+-------------------------+
Table 4: PPSPP Content Integrity Protection Methods
The "Merkle Hash Tree" method is the default for static content, see Section 5.1 ๐ฆ. "Sign All" and "Unified Merkle Tree" are for live content, see Section 6.1 ๐, and "Unified Merkle Tree" is the default ๐.
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 1 1| CIPM (8) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
7.6. Merkle Tree Hash Function ๐
okay so when the content integrity protection method is "Merkle Hash Tree", this option that defines which hash function the tree uses MUST be included ๐ง. The code for this option is 4 ๐ธ. Defined values are in Table 5 (see [FIPS180-4] for the function semantics) ๐.
+----------+-------------+
| Function | Description |
+----------+-------------+
| 0 | SHA-1 |
| 1 | SHA-224 |
| 2 | SHA-256 |
| 3 | SHA-384 |
| 4 | SHA-512 |
| 5-255 | Unassigned |
+----------+-------------+
Table 5: PPSPP Merkle Hash Functions
Implementations MUST support SHA-1 (see Section 12.5) and SHA-256 โจ. SHA-256 is the default ๐ซถ.
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 1 0 0| MHF (8) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
7.7. Live Signature Algorithm ๐
okay so when the content integrity protection method is "Sign All" or "Unified Merkle Tree", this option MUST be defined ๐ . Its code is 5, and the 8-bit value is one of the entries in the "Domain Name System Security (DNSSEC) Algorithm Numbers" registry [IANADNSSECALGNUM]. The RSASHA1 [RFC4034], RSASHA256 [RFC5702], ECDSAP256SHA256 and ECDSAP384SHA384 [RFC6605] algorithms are mandatory to implement ๐ซถ. If nothing is set, the default is ECDSAP256SHA256 โจ.
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 1 0 1| LSA (8) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
7.8. Chunk Addressing Method ๐งฉ
a peer MUST include the chunk addressing method it uses ๐. The code for this option is 6, and the defined values are listed in Table 6 below ๐.
+--------+---------------------+
| Method | Description |
+--------+---------------------+
| 0 | 32-bit bins |
| 1 | 64-bit byte ranges |
| 2 | 32-bit chunk ranges |
| 3 | 64-bit bins |
| 4 | 64-bit chunk ranges |
| 5-255 | Unassigned |
+--------+---------------------+
Table 6: PPSPP Chunk Addressing Methods
implementations MUST support "32-bit chunk ranges" and "64-bit chunk ranges" ๐. The default is "32-bit chunk ranges" ๐ฆ.
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 1 1 0| CAM (8) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
7.9. Live Discard Window ๐ช
a peer in a live swarm MUST include the discard window it uses ๐. The code for this option is 7. The unit of the discard window depends on the chunk addressing method, so check Table 6 ๐ง. For bins and chunk ranges, it counts chunks. For byte ranges, it counts bytes. Its data type is the same as for a bin, or one value in a range specification. Basically, the value is a 32-bit or 64-bit integer in big-endian format ๐ข. One important thing: if this option is used, the Chunk Addressing Method MUST appear before it in the list ๐จ. The structure looks like this:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 1 1 1| Live Discard Window (32 or 64) ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~ ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
a peer that does not, under normal circumstances, discard chunks MUST set this option to the special value 0xFFFFFFFF (32-bit) or 0xFFFFFFFFFFFFFFFF (64-bit) ๐ซง. For example, peers that record a complete broadcast and then offer it directly as a static file after the broadcast ends use these values (see Section 6.1.2) ๐ผ. Section 6.2 explains how to work out a value for this option. ๐
7.10. Supported Messages ๐ฌ
peers may support just a subset of the PPSPP messages ๐. For example, peers running over TCP may not accept ACK messages, or peers used with a centralized tracking infrastructure may not accept PEX messages. Because of that, peers who support only a proper subset of the PPSPP messages MUST signal which subset they support using this protocol option ๐ฃ. The code for this option is 8. Its value is a length octet (SupMsgLen) that gives the length, in bytes, of the compressed bitmap right after it ๐ง.
the set of supported messages can be read from the compressed bitmap by padding it with bytes of value 0 until it is 256 bits long ๐งฎ. Then a 1 bit at position X (counting left to right) means the peer supports message type X, see Table 7. To build the compressed bitmap, make a bitmap with a 1 for each supported message type and a 0 for each one that isn't, store it as an array of bytes, and cut it off after the last non-zero byte โ๏ธ. For example, the first 16 bits of the compressed bitmap for a peer that supports every message except ACKs and PEXs look like this: 11011001 11110000 ๐ธ.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 1 0 0 0| SupMsgLen (8) | ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~ Supported Messages Bitmap (variable, max 256) ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
7.11. Chunk Size ๐
a peer in a swarm MUST include the chunk size the swarm uses ๐ฐ. The code for this option is 9. Its value is a 32-bit integer giving the size of the chunks in bytes, in big-endian format. When variable chunk sizes are used, this option MUST be set to the special value 0xFFFFFFFF ๐. Section 8.1 explains how content publishers can work out a value for this option. ๐ง
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 1 0 0 1| Chunk Size (32) ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~ |
+-+-+-+-+-+-+-+-+
8. UDP Encapsulation ๐
PPSPP implementations MUST use UDP as the transport protocol, and MUST use LEDBAT for congestion control [RFC6817] ๐. Using LEDBAT lets PPSPP keep serving content after playback (seeding) without wrecking the experience of the user, who may have moved on to other tasks that use the same network connection ๐ซถ. Future PPSPP versions could also run over other transport protocols or use different congestion control algorithms ๐ฎ.
8.1. Chunk Size ๐
in general, a UDP datagram carrying PPSPP messages SHOULD fit inside a single IP packet, so its maximum size depends on the MTU of the network ๐. If the datagram doesn't fit, it's more likely to get lost, because losing one fragment means losing the whole datagram ๐.
The biggest message in a PPSPP datagram is the DATA message carrying a chunk of content. So the (maximum) size of chunk you pick for a particular swarm depends mainly on the expected MTU ๐ง . The chunk size should be chosen so that a chunk and its required INTEGRITY messages can usually fit inside a single datagram, following the Atomic Datagram Principle (Section 5.3) ๐. Hardware capabilities of the peers matter too. Having large chunks, and therefore fewer chunks per megabyte of content, cuts down on processing costs ๐ช. The chunk addressing schemes can all work with different chunk sizes, see Section 4 ๐งฉ.
The RECOMMENDED approach is to use fixed-size chunks of 1024 bytes, because this size has a high chance of travelling end-to-end across the Internet without any fragmentation ๐ฆ. In particular, with this size, a UDP datagram with a DATA message can go out as a single IP packet over an Ethernet network with 1500-byte frames ๐.
A PPSPP implementation MAY use a variant of the Packetization Layer Path MTU Discovery (PLPMTUD), described in [RFC4821], to discover the optimal MTU between sender and destination ๐. Like PLPMTUD, it sends progressively larger probing packets to find the best MTU for a given path. The twist in PPSPP is that probe packets SHOULD contain actual messages, in particular multiple DATA messages ๐ฏ. Since the probes are real DATA messages, the returning ACK messages confirm that the probe arrived, which updates the MTU estimate at both ends of the link โ . To scale up probe packets in sensible steps, a minimum chunk size of 512 bytes SHOULD be used, because smaller chunk sizes make the protocol inefficient ๐ โโ๏ธ. The upshot is that PPSPP only supports IPv4 datagrams of 576 bytes or more. This variant is not mandatory to implement ๐ซง.
The chunk size used for a particular swarm, or the fact that it is variable, MUST be part of the swarm's metadata (which then minimally consists of the swarm ID and the chunk nature and size). ๐ โจ
8.2. Datagrams and Messages ๐
when using UDP, the abstract datagram described above maps directly onto a UDP datagram ๐. Most messages inside a datagram have a fixed length, which usually depends on the message type. The first byte of a message tells you its type. The currently defined types are:
+----------+------------------+
| Msg Type | Description |
+----------+------------------+
| 0 | HANDSHAKE |
| 1 | DATA |
| 2 | ACK |
| 3 | HAVE |
| 4 | INTEGRITY |
| 5 | PEX_RESv4 |
| 6 | PEX_REQ |
| 7 | SIGNED_INTEGRITY |
| 8 | REQUEST |
| 9 | CANCEL |
| 10 | CHOKE |
| 11 | UNCHOKE |
| 12 | PEX_RESv6 |
| 13 | PEX_REScert |
| 14-254 | Unassigned |
| 255 | Reserved |
+----------+------------------+
Table 7: PPSPP Message Types
integers are serialised in network (big-endian) byte order ๐. Take a HAVE message (Section 3.2) using bin chunk addressing as an example. It has a message type of 0x03 and a payload that's a bin number, a 4-byte integer (say, 1). So its on-the-wire representation for UDP can be written in hex as "0300000001" ๐ค.
all messages are idempotent or recognisable as duplicates ๐. Idempotent means that processing a message more than once doesn't leave you in a different state than processing it just once ๐. In particular, a peer MAY resend DATA, ACK, HAVE, INTEGRITY, PEX_*, SIGNED_INTEGRITY, REQUEST, CANCEL, CHOKE, and UNCHOKE messages without problems when loss is suspected ๐. When a peer resends a HANDSHAKE message, the receiver can recognise it as a duplicate, because it already recorded the first connection attempt, and deal with it that way ๐ง.
8.3. Channels ๐ก
as described in Section 3.11, PPSPP uses a multiplexing scheme called channels, which lets multiple swarms share the same UDP port ๐. In the UDP encapsulation, each datagram from Peer A to Peer B is prefixed with the channel ID that Peer B allocated. The peers learn each other's channel IDs during the handshake, as explained in Section 3.1.1 ๐ค. A channel ID is 4 bytes long and MUST be generated following the requirements in [RFC4960] (Section 5.1.3) ๐.
8.4. HANDSHAKE ๐ค
okay so a channel gets set up with a handshake ๐ซถ to start one, the initiating peer needs two things: the swarm metadata from Section 3.1, and the IP address and UDP port of the peer it wants to talk to. the datagram carrying a HANDSHAKE message looks like this:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Channel ID (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0| Source Channel ID (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
~ Protocol Options ~
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
where's the breakdown, bestie ๐
- Destination Channel ID:
- If the datagram is sent by the initiating peer, then it MUST be an all-zeros channel ID.
- If the datagram is sent by the responding peer, then it MUST consist of the Source Channel ID from the sender's HANDSHAKE message.
- The octet
0x00: The HANDSHAKE message type - Source Channel ID: A locally unused channel ID
- Protocol Options: A list of protocol options encoding the swarm's metadata, as defined in Section 7.
okay and when you wanna close a channel ๐ช, a peer SHOULD send a HANDSHAKE message that MUST contain an all zeros Source Channel ID and a list of protocol options. the list MUST either be empty or contain the maximum version number the sender supports, following the min/max versioning scheme from [RFC6709], Section 4.1. literally so clean ๐
8.5. HAVE ๐ธ
a HAVE message (type 0x03) is giving "I've got these chunks and I checked them, they're good" โจ it has one chunk specification, which stands for a consecutive range of verified chunks. a bin is a single integer, or a chunk or byte range of two integers, and the width comes from the Chunk Addressing protocol options. everything gets encoded big-endian ๐
here's a HAVE message using 32-bit chunk ranges as the Chunk Addressing method:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 1 1| Start chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | End chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+-+-+-+-+-+-+-+-+
so the first octet is the HAVE message (0x03), then the start chunk and the end chunk that describe the chunk range ๐ฆ this diagram is a message, not a datagram, so there's no destination Channel ID stuck on the front. same goes for every message diagram after this one, so don't be confused, bestie ๐
8.6. DATA ๐
a DATA message (type 0x01) is a chunk specification, a timestamp, and the actual chunk ๐ฆ if a datagram has one DATA message, a sender MUST always put that DATA message at the tail of the datagram. a datagram MAY contain multiple DATA messages when the chunk size is fixed and none of them carry the last chunk, if that one is smaller than the chunk size. since LEDBAT congestion control is in use, a sender MUST include a timestamp, specifically a 64-bit integer for the current system time with microsecond accuracy. the timestamp MUST be included between the chunk specification and the actual chunk. the timing is SO important, it's the whole vibe ๐ฅ
here's a DATA message using 32-bit chunk ranges as the Chunk Addressing method:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 1| Start chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | End chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp (64) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
~ Data ~
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
so the first octet is the DATA message (0x01), followed by the start chunk and the end chunk describing the single chunk, then the timestamp, then the actual data ๐ง
8.7. ACK ๐
an ACK message (type 0x02) acknowledges data that its addressee received ๐ซก to play nice with the LEDBAT delay-based congestion control, an ACK message has a chunk specification and a timestamp that represents a one-way delay sample. that sample is a 64-bit integer with microsecond accuracy, and it's computed from the timestamp in the previous DATA message that carried the chunk being acknowledged, following the LEDBAT specification. the math is giving precision ๐ค
here's an ACK message using 32-bit chunk ranges as the Chunk Addressing method:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 1 0| Start chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | End chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| One-way delay sample (64) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+-+-+-+-+-+-+-+-+
where the first octet is the ACK message (0x02) followed by the start chunk and the end chunk describing the chunk range and the one-way delay sample.
8.8. INTEGRITY ๐
an INTEGRITY message (type 0x04) has a chunk specification and the cryptographic hash for the specified chunk or node ๐ง the type and format of the hash depend on the protocol options.
here's an INTEGRITY message using 32-bit chunk ranges as the Chunk Addressing method and a SHA-256 hash:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 1 0 0| Start chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | End chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
~ Hash (256) ~
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+-+-+-+-+-+-+-+-+
where the first octet is the INTEGRITY message (0x04) followed by the start chunk and the end chunk describing the chunk range and the hash.
8.9. SIGNED_INTEGRITY โ๏ธ
a SIGNED_INTEGRITY message (type 0x07) is a chunk specification, a 64-bit timestamp in NTP Timestamp format [RFC5905], and a digital signature encoded the way a Signature field is in an RRSIG record in DNSSEC, just without the Base64 encoding [RFC4034] ๐ the signature algorithm is defined by the Live Signature Algorithm protocol option, see Section 7.7. the plaintext the signature is taken over depends on the content integrity protection method being used, see Section 6.1.
here's a SIGNED_INTEGRITY message using 32-bit chunk ranges as the Chunk Addressing method:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 1 1 1| Start chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | End chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp (64) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
~ Signature ~
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
where the first octet is the SIGNED_INTEGRITY message (0x07) followed by the start chunk and the end chunk describing the chunk range, the timestamp, and the Signature.
okay the signature length is the part I'm obsessed with ๐ญ you can work it out from the Live Signature Algorithm protocol option and the swarm ID. the first mandatory algorithms are RSASHA1 and RSASHA256. for those, the swarm ID has a 1-byte Algorithm field followed by an RSA public key stored as a tuple (exponent length, exponent, modulus) [RFC3110]. given the exponent length and the length of the public key tuple in the swarm ID, you can calculate the length of the modulus in bytes. that gives you the signature length, since in RSA the signature is the length of the modulus [HAC01]. the other mandatory algorithms are ECDSAP256SHA256 and ECDSAP384SHA384 [RFC6605]. for those, the digital signature is 64 and 96 bytes, respectively. ๐ซถ๐
8.10. REQUEST ๐ฅ
okay so a REQUEST message (type 0x08) is how you tell your peer which chunks you're hoping to download ๐ฅบ it carries a chunk specification for them.
here's a REQUEST using 32-bit chunk ranges as the Chunk Addressing method:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 1 0 0 0| Start chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | End chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+-+-+-+-+-+-+-+-+
the first octet is the REQUEST message (0x08), then comes the start chunk and the end chunk, which together describe the chunk range ๐
8.11. CANCEL ๐ โโ๏ธ
a CANCEL message (type 0x09) is the vibe check of "nvm, I'm no longer interested" ๐ it has a chunk specification for the chunks the requester doesn't care about anymore.
a CANCEL using 32-bit chunk ranges as the Chunk Addressing method:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 1 0 0 1| Start chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | End chunk (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+-+-+-+-+-+-+-+-+
the first octet is the CANCEL message (0x09), followed by the start chunk and the end chunk describing the chunk range. bye bye chunks ๐
8.12. CHOKE and UNCHOKE ๐ง๐
CHOKE and UNCHOKE messages (types 0x0a and 0x0b, respectively) are giving minimalist ๐ค neither one carries a payload.
a CHOKE message:
0
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|0 0 0 0 1 0 1 0|
+-+-+-+-+-+-+-+-+
the first octet is the CHOKE message (0x0a).
an UNCHOKE message:
0
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|0 0 0 0 1 0 1 1|
+-+-+-+-+-+-+-+-+
the first octet is the UNCHOKE message (0x0b). ๐ฅณ
8.13. PEX_REQ, PEX_RESv4, PEX_RESv6, and PEX_REScert ๐๐ซถ
PEX stands for peer exchange, and it's the whole "hey, do you know anyone else in this swarm?" energy โจ
a PEX_REQ (0x06) message has no payload. a PEX_RESv4 (0x05) message contains an IPv4 address in big-endian format, followed by a UDP port number, also in big-endian format. a PEX_RESv6 (0x0c) message is the same idea, except it has a 128-bit IPv6 address instead of an IPv4 one.
now the important bit ๐จ if a PEX_REQ doesn't come from a private, unique-local, link-local, or multicast address [RFC1918] [RFC4193] [RFC4291], then the PEX_RES* messages sent back in reply MUST NOT contain such addresses. the whole point is to stop internal addresses from leaking out to external peers ๐ no oversharing, bestie.
a PEX_REQ message:
0
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|0 0 0 0 0 1 1 0|
+-+-+-+-+-+-+-+-+
the first octet is the PEX_REQ message (0x06).
a PEX_RESv4 message:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 1 0 1| IPv4 Address (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | Port (16) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
the first octet is the PEX_RESv4 message (0x05), followed by the IPv4 address and the port number.
a PEX_RESv6 message:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 1 1 0 0| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| IPv6 Address (128) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | Port (16) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
the first octet is the PEX_RESv6 message (0x0c), followed by the IPv6 address and the port number. ๐
a PEX_REScert (0x0d) message is about membership certs ๐ชช it starts with a 16-bit integer in big-endian that gives the size of the membership certificate that follows (see Section 12.2.1). this certificate says that Peer P at Time T is a member of Swarm S. it's an X.509v3 certificate [RFC5280], encoded with the ASN.1 distinguished encoding rules (DER) [CCITT.X690.2002]. the certificate MUST contain a "Subject Alternative Name" extension, marked as critical, of type uniformResourceIdentifier. no skipping that part ๐
a PEX_REScert message:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 1 1 0 1| Size of Memb. Cert. (16) | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
~ Membership Certificate ~
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
the first octet is the PEX_REScert message (0x0d), followed by the size of the membership certificate and then the membership certificate itself.
the URL in the name extension MUST follow the generic URL syntax [RFC3986]. its scheme component is "file", the host in the authority component is the DNS name or IP address of Peer P, the port in the authority component is Peer P's port, and the path holds the swarm identifier for Swarm S in hex. the preferred form of the swarm identifier is xxyyzz..., where each x, y, and z is 2 hexadecimal digits from the 8-bit pieces of the identifier. the certificate's validity time is set with notBefore UTCTime set to T, and notAfter UTCTime set to T plus some expiry time defined by the issuer โฐ an example URL:
file://192.0.2.0:6778/e5a12c7ad2d8fab33c699d1e198d66f79fa610c3
8.14. KEEPALIVE ๐
keep alives don't have a message type on UDP ๐ they're just simple datagrams made of the 4-byte channel ID of the destination, and that's it. so minimal, so iconic.
a keep-alive datagram:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Channel ID (32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
8.15. Flow and Congestion Control ๐
okay this one is lowkey my favourite ๐ฆ explicit flow control is not required for PPSPP over UDP. for video on demand, the receiver explicitly asks for the content from peers, so the receiver is the one in charge of how much data comes in ๐ for live streaming, where a push model may be used, the amount of incoming data is limited to the stream bitrate. the receiver has to be able to process that to keep playback smooth. if the receiver somehow gets saturated with data anyway, the congestion control on the sender side will notice and adjust the sending rate accordingly. the sender has its own back, honestly ๐ซถ
PPSPP over UDP can support different congestion control algorithms. right now it uses the LEDBAT congestion control algorithm [RFC6817]. LEDBAT is delay-based, and millions of people use it every day as part of the uTP transmission protocol of BitTorrent [LBT] [LCOMPL]. it's also suitable for P2P streaming [PPSPPERF] ๐ฅ
LEDBAT keeps an eye on the delay of packets on the data path. it uses the one-way delay variations to react early and limit how much congestion the stream might cause in the network [RFC6817]. this is what lets PPSPP keep serving content to other interested peers after playback has finished (seeding) without disrupting the user ๐ธ after playback, the user might switch to other tasks that use their network link, and those get priority over PPSPP traffic. so the user doesn't even notice the background PPSPP traffic, and that makes seeding the content for a longer time way more likely โจ
reacting early isn't a problem in a peer-to-peer system where multiple sources offer the content. if congestion happens near the sender, LEDBAT's early reaction does affect chunk transmission to the receiver. but honestly, the receiver benefits from learning early that transmission from a particular source is impacted. the receiver can then pick time-critical chunks from other sources during its chunk picking phase ๐ง
if the bottleneck is near the receiver, the receiver is unlucky, because any transmission from any source running through that bottleneck will back off pretty fast thanks to LEDBAT ๐ฅฒ still, for the rest of the network (and the network operator), this is a win, since the video-streaming system backs off early enough and doesn't add too much congestion.
the power of LEDBAT is that its behaviour can be configured ๐ for live streaming, a PPSPP deployer may want more aggressive behaviour to keep quality of service up. in that case, LEDBAT can be made more aggressive. specifically, LEDBAT's queuing target delay value (TARGET in [RFC6817]) and other parameters can be adjusted so it acts as aggressive as TCP (or even more). so yeah, LEDBAT is an algorithm that works for a lot of scenarios in a peer-to-peer context, and it's giving reliable ๐
8.16. Example of Operation โจ
okay bestie, let's watch a leecher and a seeder chat ๐ซถ we're sending the file "Hello World!", which fits in one 1024-byte chunk. To keep it easy to follow, we use the message names from Table 7 and the protocol option names from Table 2 instead of the raw binary values ๐
The handshake starts with the initiating peer sending a datagram that MUST start with an all-zeros channel ID (0x00000000) ๐ง Then comes a HANDSHAKE message, whose payload is locally unused, plus a random channel ID (in this case 0x00000001) and a list of protocol options. Channel IDs MUST be randomly chosen, as described in Section 12.1 ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| HANDSHAKE |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 1| Version |0 0 0 0 0 0 0 1| Min Version |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 1| Swarm ID |0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 1 0 0 0 1 1 1 1 0 1 0 0 0 0 0 0 0 0 1 0 0 1 1 1 1 1 0 0 1 1 0|
~ ..... ~
|1 0 0 0 0 1 1 0 1 0 1 0 1 0 1 0 1 0 1 1 0 0 0 0 0 0 1 1 1 0 1 1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cont. Int. |0 0 0 0 0 0 0 1| Mer.H.Tree F. |0 0 0 0 0 0 1 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Chunk Add. |0 0 0 0 0 0 1 0| Chunk Size |0 0 0 0 0 0 0 0~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0| End |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
the protocol options in that handshake are:
- Version: 1
- Minimum supported Version: 1
- Swarm Identifier: A 32-byte root hash (47a0...b03b) identifying the content
- Content Integrity Protection Method: Merkle Hash Tree
- Merkle Tree Hash Function: SHA-256
- Chunk Addressing Method: 32-bit chunk ranges
- Chunk Size: 1024
the receiving peer MAY respond ๐ and if it does, the returned datagram MUST consist of the channel ID from the sender's HANDSHAKE message (0x00000001), then a HANDSHAKE message whose payload is locally unused, then its own random channel ID (0x00000008) and a list of protocol options. After that it can send any other messages it wants ๐ฆ
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| HANDSHAKE |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 1 0 0 0| Version |0 0 0 0 0 0 0 1| Cont. Int. |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 1| Mer.H.Tree F. |0 0 0 0 0 0 1 0| Chunk Add. |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 1 0| Chunk Size |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0| End | HAVE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
with those protocol options, the receiving peer agrees to speak protocol version 1 ๐ It also picks the Merkle Hash Tree as the Content Integrity Protection Method, SHA-256 as the Merkle Tree Hash Function, 32-bit chunk ranges as the Chunk Addressing Method, and Chunk Size 1024. And it slides a HAVE message into the same datagram, announcing it already has the first chunk of content ๐
At this point the initiator knows the peer really responded ๐ฅน For that to mean anything, channel IDs MUST be random enough that nobody can easily guess them. So the third datagram of a handshake MAY already contain some heavy payload โจ To cut down on initialization round trips, the first two datagrams MAY also carry some minor payload, e.g., the HAVE message.
The initiating peer MAY send a request for the chunks it wants from the receiving peer, e.g., the first chunk announced during the handshake. It always puts the channel ID of the peer it's talking to in front of the message (0x00000008 in our example), as described in Section 3.11 ๐ It MAY also tack on extra messages, such as a PEX_REQ.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| REQUEST |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0| PEX_REQ |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
when the third datagram arrives, both peers have proof they're really talking to each other ๐ค The three-way handshake is complete! The receiving peer answers the request by sending a DATA message with the requested content ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| DATA |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 1 1 0 1 0 0 1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 1 0 0 0 0 0 1 1 0 0 0 0 0 0 0 1 0 1 1 0 1 1 1 1 1 0 1 1 0 1 1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 1 0 0 0 1 0 0|0 1 0 0 1 0 0 0 0 1 1 0 0 1 0 1 0 1 1 0 1 1 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~ ..... ~
|0 1 1 0 1 1 0 0 0 1 1 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 1 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
the DATA message is made up of:
- The 32-bit chunk range: 0,0 (the first chunk)
- The timestamp value: 0004e94180b7db44
- The data: 48656c6c6f20776f726c6421 (the "Hello world!" file)
the datagram above doesn't include an INTEGRITY message ๐ That's because the whole content fits in one message, so the initiating peer can verify it straight against the root hash.
also, in this example the peer doesn't answer the PEX_REQ, since it doesn't know any third peer in the swarm ๐
once the requested data arrives, the initiating peer replies with an ACK message for the first chunk, carrying a one-way delay sample (100 ms) โฑ๏ธ It also adds a HAVE message for that chunk ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ACK |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 1 1 0 0 1 0 0| HAVE |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
at this point the initiating peer has successfully grabbed the entire file ๐๐ Then it explicitly closes the connection by sending a HANDSHAKE message that has an all-zeros Source Channel ID ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| HANDSHAKE |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0| End |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
9. Extensibility โจ
9.1. Chunk Picking Algorithms
okay so chunk (or piece) picking is totally up to the receiving peer ๐ซถ The sending peer only finds out which chunks are preferred through REQUEST messages. In some live scenarios, it may be beneficial to let the sender ignore those hints and send unrequested data too ๐
the chunk picking algorithm sits outside PPSPP. It's usually a pluggable policy that uses the mechanisms PPSPP provides ๐ง It handles the choices the user makes while consuming the content, like seeking or switching audio tracks or subtitles. Example policies for P2P streaming can be found in [BITOS], and [EPLIVEPERF] ๐
9.2. Reciprocity Algorithms
the whole point of reciprocity algorithms in peer-to-peer systems is to encourage clients to contribute and stop freeriding ๐ค A peer counts as freeriding if it only downloads and never uploads to anyone else. Examples include tit-for-tat, as used in BitTorrent [TIT4TAT], and Give-to-Get [GIVE2GET]. In PPSPP, enforcing reciprocity is the sole responsibility of the sending peer ๐
10. IANA Considerations ๐
IANA has created a brand new top-level registry called "Peer-to-Peer Streaming Peer Protocol (PPSPP)" ๐ It holds the six new sub-registries defined below, so the protocol can be extended later. For every registry, an assignment is a name plus its associated value. Also, for every registry, the "Unassigned" ranges are governed by the policy "IETF Review", as described in [RFC5226] โจ
10.1. PPSPP Message Type Registry ๐
okay so the registry name is "PPSPP Message Type Registry" ๐ Values are integers in the range 0-255, and the initial assignments and reservations are listed in Table 7. literally just a little number list for message types, iykyk ๐ฆ
10.2. PPSPP Option Registry ๐ธ
the registry name is "PPSPP Option Registry" โจ Values are integers in the range 0-255, with initial assignments and reservations given in Table 2. same energy as the message type list, just for options ๐ซถ
10.3. PPSPP Version Number Registry ๐
registry name: "PPSPP Version Number Registry" ๐ง Values are integers in the range 0-255, and the initial assignments and reservations live in Table 3. the version numbers get their own little home, bestie ๐
10.4. PPSPP Content Integrity Protection Method Registry ๐ฅ
this one is "PPSPP Content Integrity Protection Method Registry" ๐ Values are integers in the range 0-255, with initial assignments and reservations given in Table 4. these are the integrity protection methods, and they're giving organized ๐
10.5. PPSPP Merkle Hash Tree Function Registry ๐
the registry name is "PPSPP Merkle Hash Tree Function Registry" ๐ฆ Values are integers in the range 0-255, and you'll find the initial assignments and reservations in Table 5. the hash tree functions get their own registry, and I'm obsessed โจ
10.6. PPSPP Chunk Addressing Method Registry ๐
last one! the registry name is "PPSPP Chunk Addressing Method Registry" ๐ Values are integers in the range 0-255, with initial assignments and reservations given in Table 6. the chunk addressing methods are sorted, and that's the whole IANA moment ๐ซถ
11. Manageability Considerations ๐ ๏ธโจ
this section goes over operations and management stuff, following the checklist in [RFC5706], Appendix A. it's giving very organised ops energy ๐
in this section, "PPSPP client" means a PPSPP peer acting on behalf of an end user who may not have a copy of the content yet ๐ง and "PPSPP server" means a PPSPP peer that provides the initial copies of the content to the swarm on behalf of a content provider ๐
11.1. Operations ๐ธ
11.1.1. Installation and Initial Setup ๐
a content provider who wants to distribute content with PPSPP should set up at least one PPSPP server ๐ฆ PPSPP servers need access to either some static content or some live audio/video sources. the setup process isn't standardized, to give implementors some flexibility, so do your thing ๐
the output of that process is a list of metadata records, one for each swarm ๐ A metadata record consists of the swarm ID, the chunk size used, the chunk addressing method used, the content integrity protection method used, and the Merkle hash tree function used (if applicable). if automatic content size detection (see Section 5.6) isn't used, the content length is also part of the metadata record for static content. also, the swarm ID already contains the Live Signature Algorithm used, in case of a live stream ๐ฅ
a content provider should also set up a tracking facility for the content, for example by configuring a peer-to-peer streaming protocol tracker [PPSP-TP] or a Distributed Hash Table ๐ the output of the latter process is a list of transport addresses for the tracking facility โจ
the list of metadata records for available content, plus the transport address for the tracking facility, can be shared with users in different ways. typically they get published on a website as links ๐ when a user clicks one, the PPSPP client launches, either as a standalone app or by invoking the browser's internal PPSPP protocol handler, as shown in Section 2 ๐ the clients then use the tracking facility to find the transport address of the PPSPP server(s) and other peers in the swarm, and they run the peer protocol to retrieve and redistribute the content ๐ง the format of PPSPP URLs should be defined in an extension document. the default protocol options should be used to keep the URLs small ๐
the minimum info a tracking facility has to return when it's asked for a list of peers in a swarm is this. assuming the communication between the tracking facility and the requester is protected, the facility must at least return, for each peer in the list, its IP address, transport protocol identifier (i.e., UDP), and transport protocol port number ๐ฅ
11.1.2. Migration Path ๐ฆ
this document doesn't detail a migration path, because there's no previous standard protocol that provides similar functionality ๐ซถ
11.1.3. Requirements on Other Protocols and Functional Components ๐
when using the peer-to-peer streaming protocol tracker, PPSPP requires a specific behavior from that protocol for security reasons, as detailed in Section 12.2 ๐ก๏ธ๐
11.1.4. Impact on Network Operation ๐
PPSPP is a peer-to-peer protocol that takes advantage of the fact that content is available from multiple sources, which improves robustness, scalability, and performance ๐ at the same time, poor choices about which exact sources to use can lead to a bad experience for the end user and high costs for network operators ๐ญ so PPSPP can benefit from the ALTO protocol to steer peer selection, as described in Section 3.10.1 โจ
11.1.5. Verifying Correct Operation ๐
PPSPP is operating correctly when all peers get the desired content on time ๐ so the PPSPP client is the ideal place to check that it's working. however, it isn't feasible to require logging of PPSPP peer behavior in all implementations and deployments, for example for privacy reasons ๐ there are two alternative options:
- Monitoring the PPSPP servers that initially provide the content, using standard metrics such as bandwidth usage, peer connections, and activity, can help identify trouble, see next section and [RFC2564]. ๐ง
- The tracker protocol [PPSP-TP] may be used to gather information about all peers in a swarm, to get a global view of operation, according to PPSP.OAM.REQ-3 in [RFC6972]. ๐
basic operation is easy to verify when a tracker and swarm metadata are known: just start a PPSPP download ๐ฆ deep packet inspection of DATA and ACK messages helps establish that actual content transfer is happening and that the chunk availability signaling and integrity checking are working ๐ฅ
11.1.6. Configuration โ๏ธ
Table 8 shows the PPSPP parameters, their defaults, and where each parameter is defined ๐ธ for parameters with no default, the table row contains the word "var" and refers to the section that discusses what to think about when choosing a value ๐
+-------------------------+-----------------------+-----------------+
| Name | Default | Definition |
+-------------------------+-----------------------+-----------------+
| Chunk Size | var, 1024 bytes | Section 8.1 |
| | recommended | |
| | | |
| Static Content | 1 (Merkle Hash Tree) | Section 7.5 |
| Integrity Protection | | |
| Method | | |
| | | |
| Live Content Integrity | 3 (Unified Merkle | Section 7.5 |
| Protection Method | Tree) | |
| | | |
| Merkle Hash Tree | 2 (SHA-256) | Section 7.6 |
| Function | | |
| | | |
| Live Signature | 13 (ECDSAP256SHA256) | Section 7.7 |
| Algorithm | | |
| | | |
| Chunk Addressing Method | 2 (32-bit chunk | Section 7.8 |
| | ranges) | |
| | | |
| Live Discard Window | var | Section 6.2, |
| | | Section 7.9 |
| | | |
| NCHUNKS_PER_SIG | var | Section 6.1.2.1 |
| | | |
| Dead peer detection | No reply in 3 minutes | Section 3.12 |
| | + 3 datagrams | |
+-------------------------+-----------------------+-----------------+
Table 8: PPSPP Defaults
11.2. Management Considerations ๐งโ๐ป
the management stuff for PPSPP is very similar to other protocols used for large-scale content distribution, especially HTTP ๐ how do you manage huge numbers of servers? how do you push new content out to a server farm and allow staged releases? how are faults detected, and how are servers and end-user performance measured? ngl these challenges still have standard solutions in development, so this section can't give a definitive recommendation on how PPSPP should be managed ๐ญ instead, it describes the standard solutions available right now and assumes a future extension document will provide more complete guidelines ๐ฎ
11.2.1. Management Interoperability and Information ๐
like we just said, PPSPP servers that provide initial copies of content are basically like WWW and FTP servers ๐ they can also be deployed in large numbers, so they can benefit from standard management facilities ๐ therefore, PPSPP servers may implement an SNMP management interface based on the APPLICATION-MIB [RFC2564], where the file object can be used to report on swarms ๐ง
what's missing is the ability to remove or rate limit specific PPSPP swarms on a server ๐ค this is like removing or limiting specific virtual servers on a web server. in other words, when multiple pieces of content (swarms, or virtual WWW servers) are multiplexed onto a single server process, you need more fine-grained management of that process, and that functionality is currently missing ๐ซ
logging is important for PPSPP servers and, depending on the deployment, PPSPP clients too ๐ logging should be done via syslog [RFC5424] ๐
11.2.2. Fault Management ๐จ
the facilities for verifying correct operation and server management (just covered) seem sufficient for PPSPP fault monitoring ๐ this can be supplemented with host resource monitoring [RFC2790] and UDP/IP network monitoring [RFC4113], since PPSPP server failures can generally be traced directly to conditions on the host or network ๐ฅ๏ธ
PPSPP was designed to work in a hostile environment, so many benign faults get handled by the mechanisms used for managing attacks ๐ก๏ธ for example, when a malfunctioning peer starts sending the wrong chunks, the content integrity protection mechanism detects it and another source is sought ๐ฆ
11.2.3. Configuration Management ๐
large-scale deployments may benefit from a standard way of replicating a new piece of content across a set of initial PPSPP servers ๐ธ this functionality may need controlled releasing, so content only becomes available at a specific point in time (e.g., the release of a movie trailer) ๐ฟ this could be provided via NETCONF [RFC6241], to enable atomic configuration updates over a set of servers โจ uploading the new content could be one configuration change, and making it available for the public to download could be another ๐
11.2.4. Accounting Management โจ
okay so content providers might host PPSPP for different customers and want to bill them, like based on how much bandwidth they used ๐ธ. this is basically the same vibe as billing per virtual server for web servers, so PPSPP can totally benefit from the general standardization work in this area [RFC2975] once that's actually done ๐ซถ
11.2.5. Performance Management ๐
depending on the deployment scenario, you can use the application performance measurement stuff from [RFC3729] and the related [RFC4150] with PPSPP. no cap, it's giving useful ๐ฅ
also, when the PPSPP tracker protocol is in use, it comes with its own built-in, application-level performance measurement setup for different metrics ๐ง see PPSP.OAM.REQ-3 in [RFC6972] โจ
11.2.6. Security Management ๐
malicious peers should ideally be locked out for the long term ๐ โโ๏ธ this is mostly about performance, since the protocol is already pretty robust against attacks (more on that in the next section ๐ ). Section 12.7 describes a procedure for long-term exclusion.
12. Security Considerations ๐ก๏ธ
okay bestie, this part is SERIOUS ๐ญ. like any other network protocol, PPSPP has a bunch of common security challenges to deal with. an implementation has to think about buffer overruns, DoS attacks, and manipulation (aka reflection attacks) ๐. privacy is honestly not really guaranteed, because the user is exposing their IP address to the peers. the one probable exception is when the user is hidden behind a public NAT or proxy ๐ this section digs into the protocol's security considerations in detail, so get ready ๐
12.1. Security of the Handshake Procedure ๐ค
so the analysis in [RFC5971] is the basis here, and it says PPSPP could get attacked with three kinds of denial-of-service attacks ๐ฑ
- DoS amplification attack: attackers try to use a PPSPP peer to generate more traffic aimed at a victim ๐ฃ
- DoS flood attack: attackers try to deny service to other peers by making a PPSPP peer allocate lots of state ๐๏ธ
- Disrupt service to an individual peer: attackers send bogus messages, like REQUEST and HAVE, that look like they come from victim Peer A to the Peers B1..Bn serving that peer ๐ this makes Peer A receive chunks it never asked for, or miss the chunks it did ask for ๐
the basic plan to protect against all of this is a secure handshake procedure ๐ in the UDP encapsulation, the handshake gets secured by using randomly chosen channel IDs, like this. the channel IDs must be generated following the requirements in [RFC4960] (Section 5.1.3) โจ
when UDP is used, every datagram carrying PPSPP messages is prefixed with a 4-byte channel ID. these are random numbers, set up during the handshake phase like so ๐ง Peer A starts an exchange with Peer B by sending a datagram with a HANDSHAKE message, prefixed with the channel ID made of all zeros. Peer A's HANDSHAKE includes a randomly chosen channel ID, chanA:
A->B: chan0 + HANDSHAKE(chanA) + ...
when Peer B gets this datagram, it creates some state for Peer A, which at least contains the channel ID chanA ๐๏ธ then Peer B sends a response to Peer A: a datagram with a HANDSHAKE message, prefixed with the chanA channel ID. Peer B's HANDSHAKE has a randomly chosen channel ID too, called chanB ๐ฆ
B->A: chanA + HANDSHAKE(chanB) + ...
now Peer A knows Peer B is really responding, because it echoed chanA back ๐ฅน so the next datagram Peer A sends can already carry heavy payload, aka a chunk ๐ฆ this next datagram to Peer B gets prefixed with the chanB channel ID. once Peer B receives it, both peers have proof that they're really talking to each other, and the three-way handshake is complete ๐ basically, the randomly chosen channel IDs act as tags (cf. [RFC4960] (Section 5.1)) ๐ท๏ธ
A->B: chanB + HAVE + DATA + ...
12.1.1. Protection against Attack 1 ๐ก๏ธ
short version: PPSPP does a so-called return routability check before any heavy payload gets sent ๐ this means attack 1 gets fended off. PPSPP doesn't send back much more data than it received, unless it knows it's talking to a live peer ๐ an attacker who sends a spoofed HANDSHAKE to Peer B while pretending to be Peer A would also need to intercept the message from Peer B to Peer A, so that Peer B sends heavy payload and that payload goes to the victim. that's assumed to be too hard to be a practical attack ๐ค
note, the rule is that no heavy payload may be sent until the third datagram ๐ซ this has implications for PPSPP implementations that use verbose chunk addressing schemes. if an implementation uses large bitmaps to convey chunk availability, those may not be sent by Peer B in the second datagram ๐ง
12.1.2. Protection against Attack 2 ๐จ
when Peer B receives the first datagram, it records some state about Peer A โ๏ธ right now that state is the chanA channel ID, plus the results of processing the other messages in that first datagram. specifically, if Peer A included some HAVE messages, Peer B may add a chunk availability map to Peer A's state. Peer B may also request some chunks from Peer A in the second datagram, and in that case Peer B keeps state about those outgoing requests ๐
so honestly, PPSPP is somewhat vulnerable to attack 2 ๐ฅฒ an attacker could send lots of datagrams with HANDSHAKEs and HAVEs and allocate state at the PPSPP peer that way. that's why Peer A MUST respond immediately to the second datagram, if it's still interested in Peer B โก
why use this slightly vulnerable three-way handshake instead of the safer handshake procedure from Stream Control Transmission Protocol (SCTP) [RFC4960] (Section 5.1)? speed! โฉ it gives the user a quicker response time. in the SCTP procedure, Peers A and B can't request chunks until datagrams 3 and 4 respectively, whereas in the proposed procedure it's datagrams 2 and 1. so the user waits less time between starting the video stream and seeing the first images ๐ฌโจ
12.1.3. Protection against Attack 3 ๐ต๏ธโโ๏ธ
in general, channel IDs serve to authenticate a peer ๐ชช so to pull off an attack, a malicious Peer T would need to eavesdrop on conversations between victim A and a benign Peer B, to get the channel ID Peer B assigned to Peer A, chanB ๐ on top of that, attacker Peer T would need to be able to spoof messages like REQUEST and HAVE coming from Peer A, to make Peer B send heavy DATA messages to Peer A, or to stop Peer B from sending them, respectively ๐ฌ
the capability to eavesdrop isn't common, so channel IDs will be enough protection in most cases ๐ if not, point-to-point encryption of traffic should be used, see below ๐
12.2. Secure Peer Address Exchange ๐บ๏ธ
as described in Section 3.10, Peer A can send Peer-Exchange messages PEX_RES to Peer B. these contain the IP address and port of other peers that are supposedly also in the current swarm ๐ the strength of this mechanism is that it allows decentralized tracking. after an initial bootstrap, no central tracker is needed ๐ the vulnerability of this mechanism (and DHTs) is that malicious peers can abuse it for an Amplification attack ๐
here's the sketchy part: a malicious Peer T could send PEX_RES messages to well-behaved Peer A with the addresses of Peers B1..Bn. when Peer A gets those, it could send a HANDSHAKE to all of them. so in the worst case, a single datagram results in N datagrams ๐ฅ how bad it actually gets depends on what Peer A does. if Peer A already has sufficient connections, it may not connect to the offered ones at all ๐ but if it's a fresh peer, it may connect to all of them directly ๐ฑ
PEX can also be used in Eclipse attacks [ECLIPSE], where malicious peers try to isolate a particular peer so it only interacts with malicious peers ๐ let's split this into two specific attacks:
- E1. Malicious peers try to eclipse the single injector in live streaming.
- E2. Malicious peers try to eclipse a specific consumer peer.
attack E1 has the biggest impact on the system, since it would disrupt all peers ๐จ
12.2.1. Protection against the Amplification Attack ๐
if peer addresses are relatively stable, you can get strong protection against this attack by using public key cryptography and certification ๐ specifically, a PEX_REScert message carries swarm-membership certificates instead of IP address and port. a membership certificate for Peer B states that Peer B at address (ipB,portB) is part of Swarm S at Time T, and it's cryptographically signed โ๏ธ the receiver, Peer A, can check the certificate for a valid signature, the right swarm, and liveliness, and only then consider contacting Peer B ๐ซก these swarm-membership certificates correspond to signed node descriptors in secure decentralized peer sampling services [SPS] ๐งฉ
there are several possible designs for the security environment around these membership certificates. there are different options for who signs the membership certificates and how public keys get distributed ๐ as an example, we describe a design where the peer-to-peer streaming protocol tracker acts as the certification authority ๐๏ธ
12.2.2. Example: Tracker as Certification Authority ๐๏ธ
here's how it goes ๐ Peer A wants to join Swarm S, so it sends a certificate request message to Tracker X for that swarm. once the tracker gets it, it creates a membership certificate from the request, with Swarm ID S, a Timestamp T, and the external IP and port it received the message from. it signs this with the tracker's private key, and the certificate goes back to Peer A ๐
then Peer A includes this certificate whenever it sends a PEX_REScert to Peer B. Peer B verifies it against the tracker public key. this tracker public key should be part of the swarm's metadata, which Peer B got from a trusted source ๐ after that, Peer B can pass Peer A's member certificate along to other peers in PEX_REScert messages ๐
Peer A can send the certification request when it first contacts the tracker, or at a later time โฐ the responses the tracker sends could also contain membership certificates instead of plain addresses, so they can be gossiped securely too ๐ฃ๏ธ
we assume the tracker is protected against attacks and does a return routability check. the check makes sure malicious peers can't get a certificate for a random host, only for hosts where they can eavesdrop on incoming traffic ๐๏ธ
the load on the tracker depends on churn and on how long a certificate lives. certificates can be pretty long lived, since the main goal of membership certificates is to stop malicious Peer T from making good Peer A contact random hosts ๐ โโ๏ธ the freshness of the timestamp just adds extra protection on top of that goal. it protects against malicious hosts making a good Peer A contact hosts that were previously in the swarm ๐ฐ๏ธ
the membership certificate mechanism can itself be used for a kind of amplification attack against good peers ๐ค malicious Peer T can make Peer A spend some CPU verifying the signatures on the membership certificates Peer T sends. to counter this, Peer A SHOULD check a few of the certificates sent and discard the rest if they're defective ๐งน
the same membership certificates described above can be registered in a Distributed Hash Table that has been secured against the well-known DHT specific attacks [SECDHTS] ๐๏ธ
note that this scheme doesn't work for peers behind a symmetric Network Address Translator, but normal tracker registration doesn't work for them either ๐ซ
12.2.3. Protection against Eclipse Attacks โจ
okay so before we can even talk about Eclipse attacks, we need to figure out the security vibes of the central tracker ๐ต๏ธโโ๏ธ. A tracker is vulnerable to Amplification attacks too, bestie ๐ญ. A malicious Peer T could register a victim Peer B with the tracker, and then a bunch of peers joining the swarm would all contact Peer B. Trackers can also be used for Eclipse attacks. If lots of malicious peers register themselves at the tracker, the percentage of bad peers in the returned address list could get really high ๐. We're leaving the protection of the tracker to the peer-to-peer streaming protocol tracker specification [PPSP-TP], so for the rest of this discussion we assume it returns a true random sample of the actual swarm membership (achieved via Sybil attack protection) ๐ง. That means if 50% of the peers are bad, you'll still get 50% good addresses from the tracker. iykyk ๐
Attack E1 on PEX can be fended off by letting live injectors disable PEX ๐ โโ๏ธ, or at least letting live injectors make sure part of their connections go to peers whose addresses came from the trusted tracker. The same measures defend against attack E2 on PEX too ๐. They can also be used dynamically. When the current set of Peers B that Peer A is connected to isn't giving good quality of service, Peer A can contact the tracker to find new candidates. main character energy ๐
12.3. Support for Closed Swarms ๐
Regarding PPSP.SEC.REQ-1 in [RFC6972], the Closed Swarms [CLOSED] and Enhanced Closed Swarms [ECS] mechanisms give you swarm-level access control ๐ฆ. The basic idea is that a peer cannot download from another peer unless it shows a Proof-of-Access. Enhanced Closed Swarms level up the original Closed Swarms by adding on-the-wire encryption against man-in-the-middle attacks and more flexible access control rules ๐ฅ.
The exact mapping of ECS to PPSPP is defined in [ECS-protocol] ๐.
12.4. Confidentiality of Streamed Content ๐คซ
Regarding PPSP.SEC.REQ-1 in [RFC6972], no extra mechanism is needed for confidentiality in PPSPP ๐. A content publisher who wants confidentiality should just distribute content in ciphertext, and/or in a format where Digital Rights Management (DRM) techniques have been applied ๐ . In that case, it's assumed a higher layer handles key management out-of-band. Or, if you want pure point-to-point encryption of content and traffic, you can use the proposed Closed Swarms access control mechanism, DTLS [RFC6347], or IPsec [RFC4301] ๐.
When transmitting over DTLS, PPSPP can grab the PMTU estimate maintained by the IP layer to figure out how much payload fits in a single datagram without fragmentation ([RFC6347], Section 4.1.1.1) ๐ง . If the PMTU changes and the chunk size gets too large to fit in a single datagram, PPSPP can choose to allow fragmentation by clearing the Don't Fragment (DF) bit ๐ซฃ. The other option is for the content publisher to use smaller chunks and pack multiple into the same datagram when the MTU allows โจ.
12.5. Strength of the Hash Function for Merkle Hash Trees ๐ณ
Implementations MUST support SHA-1 as the hash function for content integrity protection via Merkle hash trees ๐จ. Content providers may prefer SHA-1 over stronger hash functions because it reduces on-the-wire overhead ๐. So honestly, it's a trade-off between performance and security โ๏ธ. The security considerations for SHA-1 are discussed in [RFC6194].
Also, the hash function is used in a hash tree, which makes collisions way harder to create ๐งฉ. If attackers manage to find a collision for a hash, it can only replace one chunk, so the impact is limited ๐ฅน. If fixed-size chunks are used, the collision even has to be the same size as the original chunk. For hashes higher up in the tree, a collision must be a concatenation of two hashes. Basically, finding collisions that fit the hash tree is generally harder than finding regular collisions ๐ช.
12.6. Limit Potential Damage and Resource Exhaustion by Bad or Broken Peers ๐ง
Regarding PPSP.SEC.REQ-2 in [RFC6972], this section analyzes the potential damage a malicious peer can do with each message in the protocol, and how the protocol (implementation) prevents it ๐ก๏ธ.
12.6.1. HANDSHAKE ๐ค
- Secured against DoS Amplification attacks as described in Section 12.1 ๐.
- Threat HS.1: An Eclipse attack where Peers T1..Tn fill all connection slots of Peer A by initiating the connection to Peer A ๐ฑ.
Solution: Peer A must not let other peers fill all its available connection slots. Peer A must initiate connections itself too, to prevent isolation ๐โโ๏ธ.
12.6.2. HAVE ๐โโ๏ธ
- Threat HAVE.1: Malicious Peer T can claim to have content it doesn't have. Then Peer T won't respond to requests ๐.
Solution: Peer A will consider Peer T a slow peer and won't ask it again ๐ข.
- Threat HAVE.2: Malicious Peer T can claim not to have content. Hence, it won't contribute ๐ค.
Solution: Peer and chunk selection algorithms external to the protocol will implement fairness and provide sharing incentives ๐.
12.6.3. DATA ๐ฆ
- Threat DATA.1: Peer T sends bogus chunks ๐๏ธ.
Solution: The content integrity protection schemes defend against this ๐ซถ.
- Threat DATA.2: Peer T sends Peer A unrequested chunks ๐ .
To protect against this threat, we need network-level DoS prevention ๐งฑ.
12.6.4. ACK โ
- Threat ACK.1: Peer T acknowledges the wrong chunks ๐คก.
Solution: Peer A will detect inconsistencies with the data it sent to Peer T ๐.
- Threat ACK.2: Peer T modifies the timestamp in an ACK to Peer A, which is used for time-based congestion control โฑ๏ธ.
Solution: In theory, by decreasing the timestamp, Peer T could fake that there's no congestion when there actually is, making Peer A send more data than it should ๐ฌ. [RFC6817] does not list this as a security consideration. Possibly, this attack can be detected by the large resulting asymmetry between round-trip time and measured one-way delay ๐ต๏ธโโ๏ธ.
12.6.5. INTEGRITY and SIGNED_INTEGRITY ๐งพ
- Threat INTEGRITY.1: An amplification attack where Peer T sends bogus INTEGRITY or SIGNED_INTEGRITY messages, causing Peer A to check hashes or signatures and waste CPU for nothing ๐ฅ๐ป.
Solution: If the hashes or signatures don't check out, Peer A will stop asking Peer T because of the atomic datagram principle and the content integrity protection. Subsequent unsolicited traffic from Peer T will be ignored ๐ซ.
- Threat INTEGRITY.2: An attack where Peer T sends old SIGNED_INTEGRITY messages in the Unified Merkle Tree scheme, trying to make Peer A tune in at a past point in the live stream ๐ผ.
Solution: The timestamp in the SIGNED_INTEGRITY message protects against such replays โฐ. Subsequent traffic from Peer T will be ignored ๐ โโ๏ธ.
12.6.6. REQUEST ๐ฅ
- Threat REQUEST.1: Peer T could request lots from Peer A, leaving Peer A without resources for others ๐ค.
Solution: A limit is imposed on the upload capacity a single peer can consume, for example by using an upload bandwidth scheduler that takes into account the needs of multiple peers ๐. A natural upper limit for this upload quotum is the bitrate of the content, taking into account that this may be variable ๐๏ธ.
12.6.7. CANCEL โ
- Threat CANCEL.1: Peer T sends CANCEL messages for content it never requested to Peer A ๐คจ.
Solution: Peer A will detect the inconsistency of the messages and ignore them. Note that CANCEL messages may be received unexpectedly when a transport is used where REQUEST messages may be lost or reordered with respect to the subsequent CANCELs ๐.
12.6.8. CHOKE ๐ซ
- Threat CHOKE.1: Peer T sends REQUEST messages after Peer A has sent Peer B a CHOKE message ๐.
Solution: Peer A will just discard the unwanted REQUESTs and resend the CHOKE, assuming it got lost ๐จ.
12.6.9. UNCHOKE ๐ข
- Threat UNCHOKE.1: Peer T sends an UNCHOKE message to Peer A without having sent a CHOKE message before ๐ซ .
Solution: Peer A can easily detect this violation of protocol state and ignore it. Note this can also happen due to loss of a CHOKE message sent by a benign peer ๐ซถ.
- Threat UNCHOKE.2: Peer T sends an UNCHOKE message to Peer A, but then doesn't respond to its REQUESTs ๐ป.
Solution: Peer A will consider Peer T a slow peer and won't ask it again ๐.
12.6.10. PEX_RES ๐ง
- Secured against amplification and Eclipse attacks as described in Section 12.2 ๐ก๏ธโจ.
12.6.11. Unsolicited Messages in General ๐ฌ
- Threat: Peer T could send a spoofed PEX_REQ or REQUEST from Peer B to Peer A, causing Peer A to send a PEX_RES/DATA to Peer B ๐ญ.
Solution: The message from Peer T won't be accepted unless Peer T does a handshake first. If it does, the reply goes to Peer T, not to victim Peer B ๐ .
12.7. Exclude Bad or Broken Peers ๐ช
This section is regarding PPSP.SEC.REQ-2 in [RFC6972]. A receiving peer can detect malicious or faulty senders as just described, and then ignore them ๐. However, kicking a bad peer out of the system completely is complex ๐ฉ. Random monitoring by trusted peers that blacklist bad peers, as described in [DETMAL], is one option ๐ถ๏ธ. This mechanism does require extra capacity to run such trusted peers, which must be indistinguishable from regular peers, and it requires a solution for timely, scalable distribution of this blacklist to peers ๐ข.
13. References ๐
13.1. Normative References ๐โจ
okay so these are the references the spec needs you to have read, bestie ๐ซถ no skipping!! ๐ (the rest of the list is the optional reading, iykyk ๐)
[CCITT.X690.2002]
International Telephone and Telegraph Consultative
Committee, "ASN.1 encoding rules: Specification of basic
encoding Rules (BER), Canonical encoding rules (CER) and
Distinguished encoding rules (DER)", CCITT Recommendation
X.690, July 2002.
[FIPS180-4]
National Institute of Standards and Technology,
Information Technology Laboratory, "Federal Information
Processing Standards: Secure Hash Standard (SHS)", FIPS
PUB 180-4, March 2012.
[IANADNSSECALGNUM]
IANA, "Domain Name System Security (DNSSEC) Algorithm
Numbers", March 2014,
<http://www.iana.org/assignments/dns-sec-alg-numbers>.
[RFC1918] Rekhter, Y., Moskowitz, B., Karrenberg, D., J. de Groot,
G., and E. Lear, "Address Allocation for Private
Internets", BCP 5, RFC 1918, DOI 10.17487/RFC1918,
February 1996, <http://www.rfc-editor.org/info/rfc1918>.
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
Requirement Levels", BCP 14, RFC 2119,
DOI 10.17487/RFC2119, March 1997,
<http://www.rfc-editor.org/info/rfc2119>.
[RFC3110] Eastlake 3rd, D., "RSA/SHA-1 SIGs and RSA KEYs in the
Domain Name System (DNS)", RFC 3110, DOI 10.17487/RFC3110,
May 2001, <http://www.rfc-editor.org/info/rfc3110>.
[RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform
Resource Identifier (URI): Generic Syntax", STD 66, RFC
3986, DOI 10.17487/RFC3986, January 2005,
<http://www.rfc-editor.org/info/rfc3986>.
[RFC4034] Arends, R., Austein, R., Larson, M., Massey, D., and S.
Rose, "Resource Records for the DNS Security Extensions",
RFC 4034, DOI 10.17487/RFC4034, March 2005,
<http://www.rfc-editor.org/info/rfc4034>.
[RFC4291] Hinden, R. and S. Deering, "IP Version 6 Addressing
Architecture", RFC 4291, DOI 10.17487/RFC4291, February
2006, <http://www.rfc-editor.org/info/rfc4291>.
[RFC5280] Cooper, D., Santesson, S., Farrell, S., Boeyen, S.,
Housley, R., and W. Polk, "Internet X.509 Public Key
Infrastructure Certificate and Certificate Revocation List
(CRL) Profile", RFC 5280, DOI 10.17487/RFC5280, May 2008,
<http://www.rfc-editor.org/info/rfc5280>.
[RFC5702] Jansen, J., "Use of SHA-2 Algorithms with RSA in DNSKEY
and RRSIG Resource Records for DNSSEC", RFC 5702,
DOI 10.17487/RFC5702, October 2009,
<http://www.rfc-editor.org/info/rfc5702>.
[RFC5905] Mills, D., Martin, J., Ed., Burbank, J., and W. Kasch,
"Network Time Protocol Version 4: Protocol and Algorithms
Specification", RFC 5905, DOI 10.17487/RFC5905, June 2010,
<http://www.rfc-editor.org/info/rfc5905>.
[RFC6605] Hoffman, P. and W. Wijngaards, "Elliptic Curve Digital
Signature Algorithm (DSA) for DNSSEC", RFC 6605,
DOI 10.17487/RFC6605, April 2012,
<http://www.rfc-editor.org/info/rfc6605>.
[RFC6817] Shalunov, S., Hazel, G., Iyengar, J., and M. Kuehlewind,
"Low Extra Delay Background Transport (LEDBAT)", RFC 6817,
DOI 10.17487/RFC6817, December 2012,
<http://www.rfc-editor.org/info/rfc6817>.
these are the big-deal refs ๐ฅ the bits you really need to know before you touch the protocol ๐
13.2. Informative References โจ
okay bestie, these are the extra reads ๐๐ซถ the spec doesn't need them to work, but they're where all the good background lives ๐ฆ
- [ABMRKL] Bakker, A., "Merkle hash torrent extension", BitTorrent Enhancement Proposal 30, March 2009, <http://bittorrent.org/beps/bep_0030.html>.
- [BINMAP] Grishchenko, V. and J. Pouwelse, "Binmaps: Hybridizing Bitmaps and Binary Trees", Delft University of Technology Parallel and Distributed Systems Report Series, Report number PDS-2011-005, ISSN 1387-2109, April 2009.
- [BITOS] Vlavianos, A., Iliofotou, M., Mathieu, F., and M. Faloutsos, "BiToS: Enhancing BitTorrent for Supporting Streaming Applications", IEEE INFOCOM Global Internet Symposium, Barcelona, Spain, April 2006.
- [BITTORRENT] Cohen, B., "The BitTorrent Protocol Specification", BitTorrent Enhancement Proposal 3, February 2008, <http://bittorrent.org/beps/bep_0003.html>.
- [CLOSED] Borch, N., Mitchell, K., Arntzen, I., and D. Gabrijelcic, "Access Control to BitTorrent Swarms Using Closed Swarms", ACM workshop on Advanced Video Streaming Techniques for Peer-to-Peer Networks and Social Networking (AVSTP2P '10), Florence, Italy, October 2010, <http://doi.acm.org/10.1145/1877891.1877898>.
- [DETMAL] Shetty, S., Galdames, P., Tavanapong, W., and Ying. Cai, "Detecting Malicious Peers in Overlay Multicast Streaming", IEEE Conference on Local Computer Networks, (LCN'06), Tampa, FL, USA, November 2006.
- [ECLIPSE] Sit, E. and R. Morris, "Security Considerations for Peer-to-Peer Distributed Hash Tables", IPTPS '01: Revised Papers from the First International Workshop on Peer-to-Peer Systems, pp. 261-269, Springer-Verlag, 2002.
- [ECS] Jovanovikj, V., Gabrijelcic, D., and T. Klobucar, "Access Control in BitTorrent P2P Networks Using the Enhanced Closed Swarms Protocol", International Conference on Emerging Security Information, Systems and Technologies (SECURWARE 2011), pp. 97-102, Nice, France, August 2011.
- [ECS-protocol] Gabrijelcic, D., "Enhanced Closed Swarm protocol", Work in Progress, draft-ppsp-gabrijelcic-ecs-01, June 2013.
- [EPLIVEPERF] Bonald, T., Massoulie, L., Mathieu, F., Perino, D., and A. Twigg, "Epidemic live streaming: optimal performance trade-offs", Proceedings of the 2008 ACM SIGMETRICS International Conference on Measurement and Modeling of Computer Systems, Annapolis, MD, USA, June 2008.
- [GIVE2GET] Mol, J., Pouwelse, J., Meulpolder, M., Epema, D., and H. Sips, "Give-to-Get: Free-riding-resilient Video-on-Demand in P2P Systems", Proceedings Multimedia Computing and Networking conference (Proceedings of SPIE, Vol. 6818), San Jose, CA, USA, January 2008.
- [HAC01] Menezes, A., van Oorschot, P., and S. Vanstone, "Handbook of Applied Cryptography", CRC Press, (Fifth Printing, August 2001), October 1996.
- [JIM11] Jimenez, R., Osmani, F., and B. Knutsson, "Sub-Second Lookups on a Large-Scale Kademlia-Based Overlay", IEEE International Conference on Peer-to-Peer Computing (P2P'11), Kyoto, Japan, August 2011.
- [LBT] Rossi, D., Testa, C., Valenti, S., and L. Muscariello, "LEDBAT: the new BitTorrent congestion control protocol", Computer Communications and Networks (ICCCN), Zurich, Switzerland, August 2010.
- [LCOMPL] Testa, C. and D. Rossi, "On the impact of uTP on BitTorrent completion time", IEEE International Conference on Peer-to-Peer Computing (P2P'11), Kyoto, Japan, August 2011.
- [MERKLE] Merkle, R., "Secrecy, Authentication, and Public Key Systems", Ph.D. thesis, Dept. of Electrical Engineering, Stanford University, CA, USA, pp 40-45, 1979.
- [P2PWIKI] Bakker, A., Petrocco, R., Dale, M., Gerber, J., Grishchenko, V., Rabaioli, D., and J. Pouwelse, "Online video using BitTorrent and HTML5 applied to Wikipedia", IEEE International Conference on Peer-to-Peer Computing (P2P'10), Delft, The Netherlands, August 2010.
- [POLLIVE] Dhungel, P., Hei, Xiaojun., Ross, K., and N. Saxena, "Pollution in P2P Live Video Streaming", International Journal of Computer Networks & Communications (IJCNC) Vol. 1, No. 2, Jul 2009.
- [PPSP-TP] Cruz, R., Nunes, M., Yingjie, G., Xia, J., Huang, R., Taveira, J., and D. Lingli, "PPSP Tracker Protocol-Base Protocol (PPSP-TP/1.0)", Work in Progress, draft-ietf-ppsp-base-tracker-protocol-09, March 2015.
- [PPSPPERF] Petrocco, R., Pouwelse, J., and D. Epema, "Performance Analysis of the Libswift P2P Streaming Protocol", IEEE International Conference on Peer-to-Peer Computing (P2P'12), Tarragona, Spain, September 2012.
- [RFC2564] Kalbfleisch, C., Krupczak, C., Presuhn, R., and J. Saperia, "Application Management MIB", RFC 2564, DOI 10.17487/RFC2564, May 1999, <http://www.rfc-editor.org/info/rfc2564>.
- [RFC2790] Waldbusser, S. and P. Grillo, "Host Resources MIB", RFC 2790, DOI 10.17487/RFC2790, March 2000, <http://www.rfc-editor.org/info/rfc2790>.
- [RFC2975] Aboba, B., Arkko, J., and D. Harrington, "Introduction to Accounting Management", RFC 2975, DOI 10.17487/RFC2975, October 2000, <http://www.rfc-editor.org/info/rfc2975>.
- [RFC3365] Schiller, J., "Strong Security Requirements for Internet Engineering Task Force Standard Protocols", BCP 61, RFC 3365, DOI 10.17487/RFC3365, August 2002, <http://www.rfc-editor.org/info/rfc3365>.
- [RFC3729] Waldbusser, S., "Application Performance Measurement MIB", RFC 3729, DOI 10.17487/RFC3729, March 2004, <http://www.rfc-editor.org/info/rfc3729>.
- [RFC4113] Fenner, B. and J. Flick, "Management Information Base for the User Datagram Protocol (UDP)", RFC 4113, DOI 10.17487/RFC4113, June 2005, <http://www.rfc-editor.org/info/rfc4113>.
- [RFC4150] Dietz, R. and R. Cole, "Transport Performance Metrics MIB", RFC 4150, DOI 10.17487/RFC4150, August 2005, <http://www.rfc-editor.org/info/rfc4150>.
- [RFC4193] Hinden, R. and B. Haberman, "Unique Local IPv6 Unicast Addresses", RFC 4193, DOI 10.17487/RFC4193, October 2005, <http://www.rfc-editor.org/info/rfc4193>.
- [RFC4301] Kent, S. and K. Seo, "Security Architecture for the Internet Protocol", RFC 4301, DOI 10.17487/RFC4301, December 2005, <http://www.rfc-editor.org/info/rfc4301>.
- [RFC4821] Mathis, M. and J. Heffner, "Packetization Layer Path MTU Discovery", RFC 4821, DOI 10.17487/RFC4821, March 2007, <http://www.rfc-editor.org/info/rfc4821>.
- [RFC4960] Stewart, R., Ed., "Stream Control Transmission Protocol", RFC 4960, DOI 10.17487/RFC4960, September 2007, <http://www.rfc-editor.org/info/rfc4960>.
- [RFC5226] Narten, T. and H. Alvestrand, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26, RFC 5226, DOI 10.17487/RFC5226, May 2008, <http://www.rfc-editor.org/info/rfc5226>.
- [RFC5389] Rosenberg, J., Mahy, R., Matthews, P., and D. Wing, "Session Traversal Utilities for NAT (STUN)", RFC 5389, DOI 10.17487/RFC5389, October 2008, <http://www.rfc-editor.org/info/rfc5389>.
- [RFC5424] Gerhards, R., "The Syslog Protocol", RFC 5424, DOI 10.17487/RFC5424, March 2009, <http://www.rfc-editor.org/info/rfc5424>.
- [RFC5706] Harrington, D., "Guidelines for Considering Operations and Management of New Protocols and Protocol Extensions", RFC 5706, DOI 10.17487/RFC5706, November 2009, <http://www.rfc-editor.org/info/rfc5706>.
- [RFC5971] Schulzrinne, H. and R. Hancock, "GIST: General Internet Signalling Transport", RFC 5971, DOI 10.17487/RFC5971, October 2010, <http://www.rfc-editor.org/info/rfc5971>.
- [RFC6194] Polk, T., Chen, L., Turner, S., and P. Hoffman, "Security Considerations for the SHA-0 and SHA-1 Message-Digest Algorithms", RFC 6194, DOI 10.17487/RFC6194, March 2011, <http://www.rfc-editor.org/info/rfc6194>.
- [RFC6241] Enns, R., Ed., Bjorklund, M., Ed., Schoenwaelder, J., Ed., and A. Bierman, Ed., "Network Configuration Protocol (NETCONF)", RFC 6241, DOI 10.17487/RFC6241, June 2011, <http://www.rfc-editor.org/info/rfc6241>.
- [RFC6347] Rescorla, E. and N. Modadugu, "Datagram Transport Layer Security Version 1.2", RFC 6347, DOI 10.17487/RFC6347, January 2012, <http://www.rfc-editor.org/info/rfc6347>.
- [RFC6709] Carpenter, B., Aboba, B., Ed., and S. Cheshire, "Design Considerations for Protocol Extensions", RFC 6709, DOI 10.17487/RFC6709, September 2012, <http://www.rfc-editor.org/info/rfc6709>.
- [RFC6972] Zhang, Y. and N. Zong, "Problem Statement and Requirements of the Peer-to-Peer Streaming Protocol (PPSP)", RFC 6972, DOI 10.17487/RFC6972, July 2013, <http://www.rfc-editor.org/info/rfc6972>.
- [RFC7285] Alimi, R., Ed., Penno, R., Ed., Yang, Y., Ed., Kiesel, S., Previdi, S., Roome, W., Shalunov, S., and R. Woundy, "Application-Layer Traffic Optimization (ALTO) Protocol", RFC 7285, DOI 10.17487/RFC7285, September 2014, <http://www.rfc-editor.org/info/rfc7285>.
- [SECDHTS] Urdaneta, G., Pierre, G., and M. van Steen, "A Survey of DHT Security Techniques", ACM Computing Surveys, vol. 43(2), January 2011.
- [SIGMCAST] Wong, C. and S. Lam, "Digital Signatures for Flows and Multicasts", IEEE/ACM Transactions on Networking 7(4), pp. 502-513, August 1999.
- [SPS] Jesi, G., Montresor, A., and M. van Steen, "Secure Peer Sampling", Computer Networks vol. 54(12), pp. 2086-2098, Elsevier, August 2010.
- [SWIFTIMPL] Grishchenko, V., Paananen, J., Pronchenkov, A., Bakker, A., and R. Petrocco, "Swift reference implementation", 2015, <https://github.com/libswift/libswift>.
- [TIT4TAT] Cohen, B., "Incentives Build Robustness in BitTorrent", 1st Workshop on Economics of Peer-to-Peer Systems, Berkeley, CA, USA, May 2003.
Acknowledgements ๐
okay bestie, this is the thank-you section and it's giving gratitude ๐ซถ Arno Bakker, Riccardo Petrocco, and Victor Grishchenko were partially supported by the P2P-Next project, which is a research project at http://www.p2p-next.org/. It was funded by the European Community under its 7th Framework Programme (grant agreement no. 216217) โจ The views and conclusions in the document are the authors' own. They should not be read as the official policies or endorsements, expressed or implied, of the P2P-Next project or the European Commission ๐
PPSPP was designed by Victor Grishchenko at Technische Universiteit Delft, under supervision of Johan Pouwelse ๐ง The authors also want to thank the chairs (Martin Stiemerling, Yunfei Zhang, Stefano Previdi, and Ning Zong) and the members of the IETF PPSP working group. They also shout out Mihai Capota, Raul Jimenez, Flutra Osmani, and Raynor Vliegendhart for their contributions to this document ๐ literally everyone who helped, iconic ๐
Authors' Addresses ๐ฆ
Here are the three authors and where to find them ๐
Arno Bakker
Vrije Universiteit Amsterdam
De Boelelaan 1081
Amsterdam 1081HV
The Netherlands
Email: arno@cs.vu.nl
Riccardo Petrocco
Technische Universiteit Delft
Mekelweg 4
Delft 2628CD
The Netherlands
Email: r.petrocco@gmail.com
Victor Grishchenko
Technische Universiteit Delft
Mekelweg 4
Delft 2628CD
The Netherlands
Email: victor.grishchenko@gmail.com
๐ฆ where this page comes from
- ietf: rfc7574 2015-07
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms