Original source: 0001-banlisting.md
Summary
Punish peers by refusing to connect to their IP when they misbehave.
Motivation
Our software receives diffs, transactions, merkle tree components, etc from
other peers in the network. Much of this is authenticated, meaning we know what
the resulting hash of some object must be. This happens for the
syncable_ledger queries and the staged ledger aux data. When a peer sends us
data that results in a state where the hash doesn't match, we know that the peer
was dishonest or buggy. Some other instances of misbehavior that we want to
punish with a ban:
- A received SNARK fails to verify
- An external transition was invalid for any of a number of reasons
- A VRF proof fails to verify
When a node misbehaves, typically it is trying to mount an attack, often a denial of service attack. We can mitigate the effectiveness of these attempts by just ignoring that node's messages.
Detailed design
The basic design is pretty obvious: when a peer misbehaves, notice this, add their IP to a list, and refuse to communicate with that IP for some amount of time.
For this we have a persistent table mapping IPs to ban scores.
Introduce the banlist:
module type Banlist_intf = sig
type t
type ban =
{ host: string
; score: int
; reason: string option
; remaining_dur: Time.Span.t }
val record_misbehavior : t -> host:string -> score:int -> ?reason:string -> unit
val ban : t -> host:string -> ?reason:string -> dur:Time.Span.t -> unit
val unban : t -> host:string -> unit
val bans : t -> ban list
val lookup_score : t -> host:string -> int option
val flush : t -> unit Deferred.t
end
First, where the code currently has TODO: punish or Logger.faulty_peer, we
should insert a call to record_misbehavior. When the score for a host exceeds
some threshold, the banlist will make sure that:
- The banned hosts won't show up as a result of querying the membership layer for peers
- RPC connections from those IPs will be rejected.
The banlist should be persistent, and the CLI should allow manually adding/removing IPs from the banlist:
mina client ban add IP duration
mina client ban remove IP
mina client ban list
By default, bans will last for 1 day.
Drawbacks
In the case of bugs and not dishonesty, this could really cause chaos. In the worst case, the network can become totally disconnected and no peer will willingly communicate with any other for very long.
Rationale and alternatives
It's not clear what the ideal punishment for misbehaving nodes is. A 1-day IP ban limits most opportunities for DoS, and bitcoin does it, so it seems reasonable. The main alternative is a permaban, but this is unnecessarily harsh. Someone else is probably going to reuse that IP soon enough.
A more complex system could haves nodes sharing proofs of peer misbehavior, which would enable trustless network-wide banning. This would need a fair amount of work for probably not much gain.
Prior art
Bitcoin, when handling network messages from a peer, is careful to check a variety of conditions to ensure the message conforms to the protocol. When a check fails, a "ban score" is added to. When the ban score reaches a given threshold (default 100), the node's IP is banned for a default of 24 hours. Some checks are insta-bans (most causes of invalid blocks). See in the bitcoin core source:
src/validation.cpp, grep forstate.DoSsrc/net_processing.cpp, grep forMisbehaving
Unresolved questions
- Is there any reason to have a more sophisticated ban policy?