When you make a connection from your computer to a machine on the internet, chances are pretty good you’re sending your bits and bytes using TCP/IP, the wildly successful combination of the Internet Protocol, which handles addressing and delivery of data packets, and the Transmission Control Protocol, which ensures that the data that gets sent over the wire is received completely and in the right order.
Because TCP/IP is so ubiquitous and well-supported, it’s often the default choice for networked applications. In some cases, TCP adds too much overhead, so applications might use UDP, a much simpler protocol with no guarantees about reliability or ordering.
While TCP and UDP (together with IP) are the most common protocols in use today, they are by no means the only options. Alternatives exist at lower levels (e.g. sending raw ethernet packets or bluetooth frames), and higher levels (e.g. QUIC, which is layered over UDP).
In libp2p, we call these foundational protocols that move bits around transports, and one of libp2p’s core requirements is to be transport agnostic. This means that the decision of what transport protocol to use is up to the developer, and in fact one application can support many different transports at the same time.
Transports are defined in terms of two core operations, listening and dialing.
Listening means that you can accept incoming connections from other peers,
using whatever facility is provided by the
transport implementation. For example, a TCP transport on a unix platform could
listen system calls to have the operating system route
traffic on a given TCP port to the application.
Dialing is the process of opening an outgoing connection to a listening peer. Like listening, the specifics are determined by the implementation, but every transport in a libp2p implementation will share the same programmatic interface.
Before you can dial up a peer and open a connection, you need to know how to
reach them. Because each transport will likely require its own address scheme,
libp2p uses a convention called a “multiaddress” or
multiaddr to encode
many different addressing schemes.
The addressing doc goes into more detail, but an overview of how multiaddresses work is helpful for understanding the dial and listen interfaces.
Here’s an example of a multiaddr for a TCP/IP transport:
This is equivalent to the more familiar
18.104.22.168:6542 construction, but it
has the advantage of being explicit about the protocols that are being
described. With the multiaddr, you can see at a glance that the
address belongs to the IPv4 protocol, and the
6543 belongs to TCP.
For more complex examples, see Addressing.
Both dial and listen deal with multiaddresses. When listening, you give the transport the address you’d like to listen on, and when dialing you provide the address to dial to.
An example multiaddress that includes a
/p2p/QmcEPrat8ShnCph8WjkREzt5CPXF2RwhYxYBALDcLC1iV6 component uniquely
identifies the remote peer using the hash of its public key.
For more, see Peer Identity.
When peer routing is enabled, you can dial peers using just their PeerId, without needing to know their transport addresses before hand.
libp2p applications often need to support multiple transports at once. For example, you might want your services to be usable from long-running daemon processes via TCP, while also accepting websocket connections from peers running in a web browser.
The libp2p component responsible for managing the transports is called the switch, which also coordinates protocol negotiation, stream multiplexing, establishing secure communication and other forms of “connection upgrading”.
The switch provides a single “entry point” for dialing and listening, and frees up your application code from having to worry about the specific transports and other pieces of the “connection stack” that are used under the hood.
The term “swarm” was previously used to refer to what is now called the “switch”, and some places in the codebase still use the “swarm” terminology.