This is part two of my Gnutella exploration series. Please read this article first if you are not already familiar with Gnutella: Gnutella Explanation

In the previous article, I mentioned that Gnutella queries permeated the network by being passed along a mesh of peers, who would then pass them on to the next peer. When any of those peers found a match, they would send a query hit message back in the direction from which they received the query.
This is true for the very early versions of the protocol, but I was not being entirely honest.
That type of query routing was known as “flood routing,” and it was eventually replaced by the more advanced Query Routing Protocol, or QRP.
If you use Gnutella in 2026, your query is absolutely not being routed via flood routing as described in the original protocol spec. Many Gnutella protocol explainer articles will still tell you the network operates this way, and I believed them, too. Flood routing is a convenient lie we can tell for the sake of moving an explanation along, like the time I told my son semicolons in JavaScript are optional. If you want a P2P protocol that is used on one out of every three desktop computers, you are going to need something better. It's been proven with science and everything.
Part I of my article perpetuated lies, and I am truly sorry. I can only fit so much into one post, and flood routing can be explained in a single paragraph. Saying that flood routing is how Gnutella routes traffic is a nice, blog-friendly answer, but realistically, the network needed a more complicated protocol to scale to the millions. That was a necessity during Gnutella’s golden age in the mid-to-late 2000s.
When I implemented my own client for fun, I was tempted to use flood routing to avoid the thorny issue of query routing. I had heard of the alternative protocol, and it looked really complicated. Eventually, I saw that the majority of nodes on the network would outright refuse my connections. Connection attempts were met with a “503: Dynamic Querying Required” error message.
I was also surprised by how difficult it was to find a functioning copy of a Gnutella client that still uses flood routing. After much Internet Archive sleuthing, I found some working examples, but they are extremely old and not an accurate representation of the clients in the wild in 2026.
The original Gnutella client from Nullsoft, which did use flood routing, was released around March 2000. The oldest Query Routing Protocol document I could find was published by Christopher Rohrs in May 2002, with follow-up documents published about a year later in 2003. Accounting for the time it would have taken client maintainers to implement the new spec, I imagine that by the mid-2000s, flood routing was on its way out the door.
Flood Routing Versus the Replacement
When you are building any kind of protocol, it is important to build it in a way that people can actually implement working clients. It is not much of a protocol if there is only one implementation.
Flood routing hits the mark on this point. You can fit the whole thing in your head and implement it without a lot of fanfare. When a protocol is easy to implement, you end up with an ecosystem of diverse clients, and more importantly, people actually using the network.
This comes at a cost, though. Eventually, flood routing wasted a lot of resources because nodes in the network had to listen for every possible query, even when it was not relevant to the files they were sharing.
Engineers affiliated with LimeWire answered the call in the early 2000s by proposing a dynamic query routing protocol. It was a substantial change for the network, but once implemented, the protocol was unblocked from its previous scalability challenges, and the network grew its node count into the hundreds of millions without major changes.
Leaf and Ultrapeers

The biggest change proposed by this protocol was that the network was no longer an interlinked mesh of equal peers. A subset of peers under the new system were bigger and more important than smaller, less active peers.
It was still very much a peer-to-peer protocol, not a server-client architecture, but it did require some participants in the network to take on a larger role than others.
Clients that were less involved with the network, such as people sharing small personal file collections on a dial-up connection or behind a firewall, would take on what was known as a leaf
role. More active participants, such as people with an always-on broadband connection, a machine with a lot of RAM, or high uptime, would take on the role of ultrapeer.
Creating a two-layer network allowed the protocol to route queries far more efficiently, waste less bandwidth, and reduce the need for extra hops across the network. A tiny leaf node with low bandwidth, low uptime, and low memory would connect to several high-uptime, high-memory, high-bandwidth ultrapeers and sit at the edge of the network, while ultrapeers did the heavy lifting of query routing.
Why Two Tiers?
The Problem
Caveats
The Pipeline

