Skip to content

Commit c58f697

Browse files
rubdosmxinden
andauthored
protocols/kad: Enable filtering of (provider) records (#2163)
Introduce `KademliaStoreInserts` option, which allows to filter records. Co-authored-by: Max Inden <[email protected]>
1 parent a5b6a0b commit c58f697

File tree

4 files changed

+143
-35
lines changed

4 files changed

+143
-35
lines changed

protocols/kad/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
- Update dependencies.
44

5+
- Introduce `KademliaStoreInserts` option, which allows to filter records (see
6+
[PR 2163]).
7+
8+
[PR 2163]: https://github.com/libp2p/rust-libp2p/pull/2163
9+
510
# 0.31.0 [2021-07-12]
611

712
- Update dependencies.

protocols/kad/src/behaviour.rs

Lines changed: 90 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ pub struct Kademlia<TStore> {
6969
/// Configuration of the wire protocol.
7070
protocol_config: KademliaProtocolConfig,
7171

72+
/// Configuration of [`RecordStore`] filtering.
73+
record_filtering: KademliaStoreInserts,
74+
7275
/// The currently active (i.e. in-progress) queries.
7376
queries: QueryPool<QueryInner>,
7477

@@ -131,6 +134,29 @@ pub enum KademliaBucketInserts {
131134
Manual,
132135
}
133136

137+
/// The configurable filtering strategies for the acceptance of
138+
/// incoming records.
139+
///
140+
/// This can be used for e.g. signature verification or validating
141+
/// the accompanying [`Key`].
142+
///
143+
/// [`Key`]: crate::record::Key
144+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
145+
pub enum KademliaStoreInserts {
146+
/// Whenever a (provider) record is received,
147+
/// the record is forwarded immediately to the [`RecordStore`].
148+
Unfiltered,
149+
/// Whenever a (provider) record is received, an event is emitted.
150+
/// Provider records generate a [`KademliaEvent::InboundAddProviderRequest`],
151+
/// normal records generate a [`KademliaEvent::InboundPutRecordRequest`].
152+
///
153+
/// When deemed valid, a (provider) record needs to be explicitly stored in
154+
/// the [`RecordStore`] via [`RecordStore::put`] or [`RecordStore::add_provider`],
155+
/// whichever is applicable. A mutable reference to the [`RecordStore`] can
156+
/// be retrieved via [`Kademlia::store_mut`].
157+
FilterBoth,
158+
}
159+
134160
/// The configuration for the `Kademlia` behaviour.
135161
///
136162
/// The configuration is consumed by [`Kademlia::new`].
@@ -142,6 +168,7 @@ pub struct KademliaConfig {
142168
record_ttl: Option<Duration>,
143169
record_replication_interval: Option<Duration>,
144170
record_publication_interval: Option<Duration>,
171+
record_filtering: KademliaStoreInserts,
145172
provider_record_ttl: Option<Duration>,
146173
provider_publication_interval: Option<Duration>,
147174
connection_idle_timeout: Duration,
@@ -175,6 +202,7 @@ impl Default for KademliaConfig {
175202
record_ttl: Some(Duration::from_secs(36 * 60 * 60)),
176203
record_replication_interval: Some(Duration::from_secs(60 * 60)),
177204
record_publication_interval: Some(Duration::from_secs(24 * 60 * 60)),
205+
record_filtering: KademliaStoreInserts::Unfiltered,
178206
provider_publication_interval: Some(Duration::from_secs(12 * 60 * 60)),
179207
provider_record_ttl: Some(Duration::from_secs(24 * 60 * 60)),
180208
connection_idle_timeout: Duration::from_secs(10),
@@ -259,6 +287,15 @@ impl KademliaConfig {
259287
self
260288
}
261289

290+
/// Sets whether or not records should be filtered before being stored.
291+
///
292+
/// See [`KademliaStoreInserts`] for the different values.
293+
/// Defaults to [`KademliaStoreInserts::Unfiltered`].
294+
pub fn set_record_filtering(&mut self, filtering: KademliaStoreInserts) -> &mut Self {
295+
self.record_filtering = filtering;
296+
self
297+
}
298+
262299
/// Sets the (re-)replication interval for stored records.
263300
///
264301
/// Periodic replication of stored records ensures that the records
@@ -393,6 +430,7 @@ where
393430
kbuckets: KBucketsTable::new(local_key, config.kbucket_pending_timeout),
394431
kbucket_inserts: config.kbucket_inserts,
395432
protocol_config: config.protocol_config,
433+
record_filtering: config.record_filtering,
396434
queued_events: VecDeque::with_capacity(config.query_config.replication_factor.get()),
397435
queries: QueryPool::new(config.query_config),
398436
connected_peers: Default::default(),
@@ -1572,22 +1610,33 @@ where
15721610
// The record is cloned because of the weird libp2p protocol
15731611
// requirement to send back the value in the response, although this
15741612
// is a waste of resources.
1575-
match self.store.put(record.clone()) {
1576-
Ok(()) => debug!(
1577-
"Record stored: {:?}; {} bytes",
1578-
record.key,
1579-
record.value.len()
1580-
),
1581-
Err(e) => {
1582-
info!("Record not stored: {:?}", e);
1613+
match self.record_filtering {
1614+
KademliaStoreInserts::Unfiltered => match self.store.put(record.clone()) {
1615+
Ok(()) => debug!(
1616+
"Record stored: {:?}; {} bytes",
1617+
record.key,
1618+
record.value.len()
1619+
),
1620+
Err(e) => {
1621+
info!("Record not stored: {:?}", e);
1622+
self.queued_events
1623+
.push_back(NetworkBehaviourAction::NotifyHandler {
1624+
peer_id: source,
1625+
handler: NotifyHandler::One(connection),
1626+
event: KademliaHandlerIn::Reset(request_id),
1627+
});
1628+
return;
1629+
}
1630+
},
1631+
KademliaStoreInserts::FilterBoth => {
15831632
self.queued_events
1584-
.push_back(NetworkBehaviourAction::NotifyHandler {
1585-
peer_id: source,
1586-
handler: NotifyHandler::One(connection),
1587-
event: KademliaHandlerIn::Reset(request_id),
1588-
});
1589-
1590-
return;
1633+
.push_back(NetworkBehaviourAction::GenerateEvent(
1634+
KademliaEvent::InboundPutRecordRequest {
1635+
source,
1636+
connection,
1637+
record: record.clone(),
1638+
},
1639+
));
15911640
}
15921641
}
15931642
}
@@ -1620,8 +1669,18 @@ where
16201669
expires: self.provider_record_ttl.map(|ttl| Instant::now() + ttl),
16211670
addresses: provider.multiaddrs,
16221671
};
1623-
if let Err(e) = self.store.add_provider(record) {
1624-
info!("Provider record not stored: {:?}", e);
1672+
match self.record_filtering {
1673+
KademliaStoreInserts::Unfiltered => {
1674+
if let Err(e) = self.store.add_provider(record) {
1675+
info!("Provider record not stored: {:?}", e);
1676+
}
1677+
}
1678+
KademliaStoreInserts::FilterBoth => {
1679+
self.queued_events
1680+
.push_back(NetworkBehaviourAction::GenerateEvent(
1681+
KademliaEvent::InboundAddProviderRequest { record },
1682+
));
1683+
}
16251684
}
16261685
}
16271686
}
@@ -2257,6 +2316,20 @@ pub struct PeerRecord {
22572316
/// See [`NetworkBehaviour::poll`].
22582317
#[derive(Debug)]
22592318
pub enum KademliaEvent {
2319+
/// A peer sent a [`KademliaHandlerIn::PutRecord`] request and filtering is enabled.
2320+
///
2321+
/// See [`KademliaStoreInserts`] and [`KademliaConfig::set_record_filtering`].
2322+
InboundPutRecordRequest {
2323+
source: PeerId,
2324+
connection: ConnectionId,
2325+
record: Record,
2326+
},
2327+
2328+
/// A peer sent a [`KademliaHandlerIn::AddProvider`] request and filtering [`KademliaStoreInserts::FilterBoth`] is enabled.
2329+
///
2330+
/// See [`KademliaStoreInserts`] and [`KademliaConfig::set_record_filtering`] for details..
2331+
InboundAddProviderRequest { record: ProviderRecord },
2332+
22602333
/// An inbound request has been received and handled.
22612334
//
22622335
// Note on the difference between 'request' and 'query': A request is a

protocols/kad/src/behaviour/test.rs

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ fn get_record_not_found() {
488488
/// is equal to the configured replication factor.
489489
#[test]
490490
fn put_record() {
491-
fn prop(records: Vec<Record>, seed: Seed) {
491+
fn prop(records: Vec<Record>, seed: Seed, filter_records: bool, drop_records: bool) {
492492
let mut rng = StdRng::from_seed(seed.0);
493493
let replication_factor =
494494
NonZeroUsize::new(rng.gen_range(1, (K_VALUE.get() / 2) + 1)).unwrap();
@@ -501,6 +501,10 @@ fn put_record() {
501501
config.disjoint_query_paths(true);
502502
}
503503

504+
if filter_records {
505+
config.set_record_filtering(KademliaStoreInserts::FilterBoth);
506+
}
507+
504508
let mut swarms = {
505509
let mut fully_connected_swarms =
506510
build_fully_connected_nodes_with_config(num_total - 1, config.clone());
@@ -596,6 +600,22 @@ fn put_record() {
596600
}
597601
}
598602
}
603+
Poll::Ready(Some(SwarmEvent::Behaviour(
604+
KademliaEvent::InboundPutRecordRequest { record, .. },
605+
))) => {
606+
assert_ne!(
607+
swarm.behaviour().record_filtering,
608+
KademliaStoreInserts::Unfiltered
609+
);
610+
if !drop_records {
611+
// Accept the record
612+
swarm
613+
.behaviour_mut()
614+
.store_mut()
615+
.put(record)
616+
.expect("record is stored");
617+
}
618+
}
599619
// Ignore any other event.
600620
Poll::Ready(Some(_)) => (),
601621
e @ Poll::Ready(_) => panic!("Unexpected return value: {:?}", e),
@@ -650,21 +670,29 @@ fn put_record() {
650670
})
651671
.collect::<HashSet<_>>();
652672

653-
assert_eq!(actual.len(), replication_factor.get());
654-
655-
let actual_not_expected = actual.difference(&expected).collect::<Vec<&PeerId>>();
656-
assert!(
657-
actual_not_expected.is_empty(),
658-
"Did not expect records to be stored on nodes {:?}.",
659-
actual_not_expected,
660-
);
661-
662-
let expected_not_actual = expected.difference(&actual).collect::<Vec<&PeerId>>();
663-
assert!(
664-
expected_not_actual.is_empty(),
665-
"Expected record to be stored on nodes {:?}.",
666-
expected_not_actual,
667-
);
673+
if swarms[0].behaviour().record_filtering != KademliaStoreInserts::Unfiltered
674+
&& drop_records
675+
{
676+
assert_eq!(actual.len(), 0);
677+
} else {
678+
assert_eq!(actual.len(), replication_factor.get());
679+
680+
let actual_not_expected =
681+
actual.difference(&expected).collect::<Vec<&PeerId>>();
682+
assert!(
683+
actual_not_expected.is_empty(),
684+
"Did not expect records to be stored on nodes {:?}.",
685+
actual_not_expected,
686+
);
687+
688+
let expected_not_actual =
689+
expected.difference(&actual).collect::<Vec<&PeerId>>();
690+
assert!(
691+
expected_not_actual.is_empty(),
692+
"Expected record to be stored on nodes {:?}.",
693+
expected_not_actual,
694+
);
695+
}
668696
}
669697

670698
if republished {
@@ -692,7 +720,9 @@ fn put_record() {
692720
}))
693721
}
694722

695-
QuickCheck::new().tests(3).quickcheck(prop as fn(_, _) -> _)
723+
QuickCheck::new()
724+
.tests(4)
725+
.quickcheck(prop as fn(_, _, _, _) -> _)
696726
}
697727

698728
#[test]

protocols/kad/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub use behaviour::{
4848
QueryStats,
4949
};
5050
pub use behaviour::{
51-
Kademlia, KademliaBucketInserts, KademliaCaching, KademliaConfig, KademliaEvent, Quorum,
51+
Kademlia, KademliaCaching, KademliaConfig, KademliaEvent, KademliaStoreInserts, Quorum,
5252
};
5353
pub use protocol::KadConnectionType;
5454
pub use query::QueryId;

0 commit comments

Comments
 (0)