mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-21 03:21:08 +03:00
Peer rate limiting (#1933)
* Rate counter in peer conn to monitor traffic * Ban peers that trigger is_abusive
This commit is contained in:
parent
d2088ff48c
commit
8b546632fe
7 changed files with 141 additions and 80 deletions
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2018-2018 The Grin Developers
|
// Copyright 2018 The Grin Developers
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -26,11 +26,11 @@ use std::mem::size_of;
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
use std::sync::{mpsc, Arc};
|
use std::sync::{mpsc, Arc};
|
||||||
use std::{cmp, thread, time};
|
use std::{cmp, thread, time};
|
||||||
use util::RwLock;
|
|
||||||
|
|
||||||
use core::ser;
|
use core::ser;
|
||||||
use msg::{read_body, read_exact, read_header, write_all, write_to_buf, MsgHeader, Type};
|
use msg::{read_body, read_exact, read_header, write_all, write_to_buf, MsgHeader, Type};
|
||||||
use types::Error;
|
use types::Error;
|
||||||
|
use util::{RateCounter, RwLock};
|
||||||
|
|
||||||
/// A trait to be implemented in order to receive messages from the
|
/// A trait to be implemented in order to receive messages from the
|
||||||
/// connection. Allows providing an optional response.
|
/// connection. Allows providing an optional response.
|
||||||
|
@ -146,9 +146,9 @@ pub const SEND_CHANNEL_CAP: usize = 10;
|
||||||
|
|
||||||
pub struct Tracker {
|
pub struct Tracker {
|
||||||
/// Bytes we've sent.
|
/// Bytes we've sent.
|
||||||
pub sent_bytes: Arc<RwLock<u64>>,
|
pub sent_bytes: Arc<RwLock<RateCounter>>,
|
||||||
/// Bytes we've received.
|
/// Bytes we've received.
|
||||||
pub received_bytes: Arc<RwLock<u64>>,
|
pub received_bytes: Arc<RwLock<RateCounter>>,
|
||||||
/// Channel to allow sending data through the connection
|
/// Channel to allow sending data through the connection
|
||||||
pub send_channel: mpsc::SyncSender<Vec<u8>>,
|
pub send_channel: mpsc::SyncSender<Vec<u8>>,
|
||||||
/// Channel to close the connection
|
/// Channel to close the connection
|
||||||
|
@ -168,7 +168,7 @@ impl Tracker {
|
||||||
|
|
||||||
// Increase sent bytes counter
|
// Increase sent bytes counter
|
||||||
let mut sent_bytes = self.sent_bytes.write();
|
let mut sent_bytes = self.sent_bytes.write();
|
||||||
*sent_bytes += buf_len as u64;
|
sent_bytes.inc(buf_len as u64);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ where
|
||||||
let (error_tx, error_rx) = mpsc::channel();
|
let (error_tx, error_rx) = mpsc::channel();
|
||||||
|
|
||||||
// Counter of number of bytes received
|
// Counter of number of bytes received
|
||||||
let received_bytes = Arc::new(RwLock::new(0));
|
let received_bytes = Arc::new(RwLock::new(RateCounter::new()));
|
||||||
|
|
||||||
stream
|
stream
|
||||||
.set_nonblocking(true)
|
.set_nonblocking(true)
|
||||||
|
@ -201,7 +201,7 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
Tracker {
|
Tracker {
|
||||||
sent_bytes: Arc::new(RwLock::new(0)),
|
sent_bytes: Arc::new(RwLock::new(RateCounter::new())),
|
||||||
received_bytes: received_bytes.clone(),
|
received_bytes: received_bytes.clone(),
|
||||||
send_channel: send_tx,
|
send_channel: send_tx,
|
||||||
close_channel: close_tx,
|
close_channel: close_tx,
|
||||||
|
@ -215,7 +215,7 @@ fn poll<H>(
|
||||||
send_rx: mpsc::Receiver<Vec<u8>>,
|
send_rx: mpsc::Receiver<Vec<u8>>,
|
||||||
error_tx: mpsc::Sender<Error>,
|
error_tx: mpsc::Sender<Error>,
|
||||||
close_rx: mpsc::Receiver<()>,
|
close_rx: mpsc::Receiver<()>,
|
||||||
received_bytes: Arc<RwLock<u64>>,
|
received_bytes: Arc<RwLock<RateCounter>>,
|
||||||
) where
|
) where
|
||||||
H: MessageHandler,
|
H: MessageHandler,
|
||||||
{
|
{
|
||||||
|
@ -240,8 +240,7 @@ fn poll<H>(
|
||||||
// Increase received bytes counter
|
// Increase received bytes counter
|
||||||
{
|
{
|
||||||
let mut received_bytes = received_bytes.write();
|
let mut received_bytes = received_bytes.write();
|
||||||
let header_size = size_of::<MsgHeader>() as u64;
|
received_bytes.inc(size_of::<MsgHeader>() as u64 + msg.header.msg_len);
|
||||||
*received_bytes += header_size + msg.header.msg_len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(Some(resp)) = try_break!(error_tx, handler.consume(msg)) {
|
if let Some(Some(resp)) = try_break!(error_tx, handler.consume(msg)) {
|
||||||
|
@ -249,25 +248,15 @@ fn poll<H>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the write end
|
// check the write end, use or_else so try_recv is lazily eval'd
|
||||||
if let Ok::<Vec<u8>, ()>(data) = retry_send {
|
let maybe_data = retry_send.or_else(|_| send_rx.try_recv());
|
||||||
if let None =
|
retry_send = Err(());
|
||||||
try_break!(error_tx, conn.write_all(&data[..]).map_err(&From::from))
|
if let Ok(data) = maybe_data {
|
||||||
{
|
let written =
|
||||||
|
try_break!(error_tx, conn.write_all(&data[..]).map_err(&From::from));
|
||||||
|
if written.is_none() {
|
||||||
retry_send = Ok(data);
|
retry_send = Ok(data);
|
||||||
} else {
|
|
||||||
retry_send = Err(());
|
|
||||||
}
|
}
|
||||||
} else if let Ok(data) = send_rx.try_recv() {
|
|
||||||
if let None =
|
|
||||||
try_break!(error_tx, conn.write_all(&data[..]).map_err(&From::from))
|
|
||||||
{
|
|
||||||
retry_send = Ok(data);
|
|
||||||
} else {
|
|
||||||
retry_send = Err(());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
retry_send = Err(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the close channel
|
// check the close channel
|
||||||
|
|
|
@ -30,6 +30,7 @@ use types::{
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_TRACK_SIZE: usize = 30;
|
const MAX_TRACK_SIZE: usize = 30;
|
||||||
|
const MAX_PEER_MSG_PER_MIN: u64 = 300;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
/// Remind: don't mix up this 'State' with that 'State' in p2p/src/store.rs,
|
/// Remind: don't mix up this 'State' with that 'State' in p2p/src/store.rs,
|
||||||
|
@ -149,22 +150,35 @@ impl Peer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the peer is considered abusive, mostly for spammy nodes
|
||||||
|
pub fn is_abusive(&self) -> bool {
|
||||||
|
if let Some(ref conn) = self.connection {
|
||||||
|
let conn = conn.lock();
|
||||||
|
let rec = conn.received_bytes.read();
|
||||||
|
let sent = conn.sent_bytes.read();
|
||||||
|
rec.count_per_min() > MAX_PEER_MSG_PER_MIN
|
||||||
|
|| sent.count_per_min() > MAX_PEER_MSG_PER_MIN
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Number of bytes sent to the peer
|
/// Number of bytes sent to the peer
|
||||||
pub fn sent_bytes(&self) -> Option<u64> {
|
pub fn last_min_sent_bytes(&self) -> Option<u64> {
|
||||||
if let Some(ref tracker) = self.connection {
|
if let Some(ref tracker) = self.connection {
|
||||||
let conn = tracker.lock();
|
let conn = tracker.lock();
|
||||||
let sent_bytes = conn.sent_bytes.read();
|
let sent_bytes = conn.sent_bytes.read();
|
||||||
return Some(*sent_bytes);
|
return Some(sent_bytes.bytes_per_min());
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Number of bytes received from the peer
|
/// Number of bytes received from the peer
|
||||||
pub fn received_bytes(&self) -> Option<u64> {
|
pub fn last_min_received_bytes(&self) -> Option<u64> {
|
||||||
if let Some(ref tracker) = self.connection {
|
if let Some(ref tracker) = self.connection {
|
||||||
let conn = tracker.lock();
|
let conn = tracker.lock();
|
||||||
let received_bytes = conn.received_bytes.read();
|
let received_bytes = conn.received_bytes.read();
|
||||||
return Some(*received_bytes);
|
return Some(received_bytes.bytes_per_min());
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -385,52 +385,45 @@ impl Peers {
|
||||||
for peer in self.peers.read().values() {
|
for peer in self.peers.read().values() {
|
||||||
if peer.is_banned() {
|
if peer.is_banned() {
|
||||||
debug!("clean_peers {:?}, peer banned", peer.info.addr);
|
debug!("clean_peers {:?}, peer banned", peer.info.addr);
|
||||||
rm.push(peer.clone());
|
rm.push(peer.info.addr.clone());
|
||||||
} else if !peer.is_connected() {
|
} else if !peer.is_connected() {
|
||||||
debug!("clean_peers {:?}, not connected", peer.info.addr);
|
debug!("clean_peers {:?}, not connected", peer.info.addr);
|
||||||
rm.push(peer.clone());
|
rm.push(peer.info.addr.clone());
|
||||||
|
} else if peer.is_abusive() {
|
||||||
|
debug!("clean_peers {:?}, abusive", peer.info.addr);
|
||||||
|
let _ = self.update_state(peer.info.addr, State::Banned);
|
||||||
|
rm.push(peer.info.addr.clone());
|
||||||
} else {
|
} else {
|
||||||
let (stuck, diff) = peer.is_stuck();
|
let (stuck, diff) = peer.is_stuck();
|
||||||
if stuck && diff < self.adapter.total_difficulty() {
|
if stuck && diff < self.adapter.total_difficulty() {
|
||||||
debug!("clean_peers {:?}, stuck peer", peer.info.addr);
|
debug!("clean_peers {:?}, stuck peer", peer.info.addr);
|
||||||
peer.stop();
|
|
||||||
let _ = self.update_state(peer.info.addr, State::Defunct);
|
let _ = self.update_state(peer.info.addr, State::Defunct);
|
||||||
rm.push(peer.clone());
|
rm.push(peer.info.addr.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure we do not still have too many connected peers
|
||||||
|
let excess_count = (self.peer_count() as usize - rm.len()).saturating_sub(max_count);
|
||||||
|
if excess_count > 0 {
|
||||||
|
// map peers to addrs in a block to bound how long we keep the read lock for
|
||||||
|
let mut addrs = self
|
||||||
|
.connected_peers()
|
||||||
|
.iter()
|
||||||
|
.take(excess_count)
|
||||||
|
.map(|x| x.info.addr.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
rm.append(&mut addrs);
|
||||||
|
}
|
||||||
|
|
||||||
// now clean up peer map based on the list to remove
|
// now clean up peer map based on the list to remove
|
||||||
{
|
{
|
||||||
let mut peers = self.peers.write();
|
let mut peers = self.peers.write();
|
||||||
for p in rm {
|
for p in rm {
|
||||||
peers.remove(&p.info.addr);
|
let _ = peers.get(&p).map(|p| p.stop());
|
||||||
|
peers.remove(&p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure we do not have too many connected peers
|
|
||||||
let excess_count = {
|
|
||||||
let peer_count = self.peer_count() as usize;
|
|
||||||
if peer_count > max_count {
|
|
||||||
peer_count - max_count
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// map peers to addrs in a block to bound how long we keep the read lock for
|
|
||||||
let addrs = self
|
|
||||||
.connected_peers()
|
|
||||||
.iter()
|
|
||||||
.map(|x| x.info.addr.clone())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// now remove them taking a short-lived write lock each time
|
|
||||||
// maybe better to take write lock once and remove them all?
|
|
||||||
for x in addrs.iter().take(excess_count) {
|
|
||||||
let mut peers = self.peers.write();
|
|
||||||
peers.remove(x);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(&self) {
|
pub fn stop(&self) {
|
||||||
|
|
|
@ -150,9 +150,9 @@ pub struct PeerStats {
|
||||||
/// Last time we saw a ping/pong from this peer.
|
/// Last time we saw a ping/pong from this peer.
|
||||||
pub last_seen: DateTime<Utc>,
|
pub last_seen: DateTime<Utc>,
|
||||||
/// Number of bytes we've sent to the peer.
|
/// Number of bytes we've sent to the peer.
|
||||||
pub sent_bytes: Option<u64>,
|
pub sent_bytes_per_sec: u64,
|
||||||
/// Number of bytes we've received from the peer.
|
/// Number of bytes we've received from the peer.
|
||||||
pub received_bytes: Option<u64>,
|
pub received_bytes_per_sec: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StratumStats {
|
impl StratumStats {
|
||||||
|
@ -186,8 +186,8 @@ impl PeerStats {
|
||||||
height: peer.info.height(),
|
height: peer.info.height(),
|
||||||
direction: direction.to_string(),
|
direction: direction.to_string(),
|
||||||
last_seen: peer.info.last_seen(),
|
last_seen: peer.info.last_seen(),
|
||||||
sent_bytes: peer.sent_bytes(),
|
sent_bytes_per_sec: peer.last_min_sent_bytes().unwrap_or(0) / 60,
|
||||||
received_bytes: peer.received_bytes(),
|
received_bytes_per_sec: peer.last_min_received_bytes().unwrap_or(0) / 60,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,16 +57,8 @@ impl PeerColumn {
|
||||||
impl TableViewItem<PeerColumn> for PeerStats {
|
impl TableViewItem<PeerColumn> for PeerStats {
|
||||||
fn to_column(&self, column: PeerColumn) -> String {
|
fn to_column(&self, column: PeerColumn) -> String {
|
||||||
// Converts optional size to human readable size
|
// Converts optional size to human readable size
|
||||||
fn size_to_string(size: Option<u64>) -> String {
|
fn size_to_string(size: u64) -> String {
|
||||||
if let Some(n) = size {
|
size.file_size(CONVENTIONAL).unwrap_or("-".to_string())
|
||||||
let size = n.file_size(CONVENTIONAL);
|
|
||||||
match size {
|
|
||||||
Ok(size) => size,
|
|
||||||
Err(_) => "-".to_string(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"-".to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match column {
|
match column {
|
||||||
|
@ -74,8 +66,8 @@ impl TableViewItem<PeerColumn> for PeerStats {
|
||||||
PeerColumn::State => self.state.clone(),
|
PeerColumn::State => self.state.clone(),
|
||||||
PeerColumn::UsedBandwidth => format!(
|
PeerColumn::UsedBandwidth => format!(
|
||||||
"S: {}, R: {}",
|
"S: {}, R: {}",
|
||||||
size_to_string(self.sent_bytes),
|
size_to_string(self.sent_bytes_per_sec),
|
||||||
size_to_string(self.received_bytes),
|
size_to_string(self.received_bytes_per_sec),
|
||||||
).to_string(),
|
).to_string(),
|
||||||
PeerColumn::TotalDifficulty => format!(
|
PeerColumn::TotalDifficulty => format!(
|
||||||
"{} D @ {} H ({}s)",
|
"{} D @ {} H ({}s)",
|
||||||
|
@ -94,10 +86,10 @@ impl TableViewItem<PeerColumn> for PeerStats {
|
||||||
{
|
{
|
||||||
// Compares used bandwidth of two peers
|
// Compares used bandwidth of two peers
|
||||||
fn cmp_used_bandwidth(curr: &PeerStats, other: &PeerStats) -> Ordering {
|
fn cmp_used_bandwidth(curr: &PeerStats, other: &PeerStats) -> Ordering {
|
||||||
let curr_recv_bytes = curr.received_bytes.unwrap_or(0);
|
let curr_recv_bytes = curr.received_bytes_per_sec;
|
||||||
let curr_sent_bytes = curr.sent_bytes.unwrap_or(0);
|
let curr_sent_bytes = curr.sent_bytes_per_sec;
|
||||||
let other_recv_bytes = other.received_bytes.unwrap_or(0);
|
let other_recv_bytes = other.received_bytes_per_sec;
|
||||||
let other_sent_bytes = other.sent_bytes.unwrap_or(0);
|
let other_sent_bytes = other.sent_bytes_per_sec;
|
||||||
|
|
||||||
let curr_sum = curr_recv_bytes + curr_sent_bytes;
|
let curr_sum = curr_recv_bytes + curr_sent_bytes;
|
||||||
let other_sum = other_recv_bytes + other_sent_bytes;
|
let other_sum = other_recv_bytes + other_sent_bytes;
|
||||||
|
|
|
@ -71,6 +71,9 @@ pub mod file;
|
||||||
/// Compress and decompress zip bz2 archives
|
/// Compress and decompress zip bz2 archives
|
||||||
pub mod zip;
|
pub mod zip;
|
||||||
|
|
||||||
|
mod rate_counter;
|
||||||
|
pub use rate_counter::RateCounter;
|
||||||
|
|
||||||
/// Encapsulation of a RwLock<Option<T>> for one-time initialization.
|
/// Encapsulation of a RwLock<Option<T>> for one-time initialization.
|
||||||
/// This implementation will purposefully fail hard if not used
|
/// This implementation will purposefully fail hard if not used
|
||||||
/// properly, for example if not initialized before being first used
|
/// properly, for example if not initialized before being first used
|
||||||
|
|
70
util/src/rate_counter.rs
Normal file
70
util/src/rate_counter.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
// Copyright 2018 The Grin Developers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
/// Utility to track the rate of data transfers
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
/// A rate counter tracks the number of transfers, the amount of data
|
||||||
|
/// exchanged and the rate of transfer (via a few timers) over the last
|
||||||
|
/// minute. The counter does not try to be accurate and update times
|
||||||
|
/// proactively, instead it only does so lazily. As a result, produced
|
||||||
|
/// rates are worst-case estimates.
|
||||||
|
pub struct RateCounter {
|
||||||
|
last_min_bytes: Vec<u64>,
|
||||||
|
last_min_count: u64,
|
||||||
|
last_min_times: Vec<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RateCounter {
|
||||||
|
/// Instantiate a new rate counter
|
||||||
|
pub fn new() -> RateCounter {
|
||||||
|
RateCounter {
|
||||||
|
last_min_bytes: vec![],
|
||||||
|
last_min_count: 0,
|
||||||
|
last_min_times: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Increments number of bytes transferred, updating counts and rates.
|
||||||
|
pub fn inc(&mut self, bytes: u64) {
|
||||||
|
let now_millis = millis_since_epoch();
|
||||||
|
self.last_min_times.push(now_millis);
|
||||||
|
self.last_min_bytes.push(bytes);
|
||||||
|
self.last_min_count += 1;
|
||||||
|
while self.last_min_times.len() > 0 && self.last_min_times[0] > now_millis + 60000 {
|
||||||
|
self.last_min_times.pop();
|
||||||
|
self.last_min_bytes.pop();
|
||||||
|
self.last_min_count -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of bytes counted in the last minute
|
||||||
|
pub fn bytes_per_min(&self) -> u64 {
|
||||||
|
self.last_min_bytes.iter().sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count of increases in the last minute
|
||||||
|
pub fn count_per_min(&self) -> u64 {
|
||||||
|
self.last_min_count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// turns out getting the millisecs since epoch in Rust isn't as easy as it
|
||||||
|
// could be
|
||||||
|
fn millis_since_epoch() -> u64 {
|
||||||
|
let since_epoch = SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.unwrap_or(Duration::new(0, 0));
|
||||||
|
since_epoch.as_secs() * 1000 + since_epoch.subsec_millis() as u64
|
||||||
|
}
|
Loading…
Reference in a new issue