Skip to content

Commit 994a61b

Browse files
authored
Respond to requests from peers with invalid ENRs (#265)
* Respond to requests from peers with invalid ENRs * Remove old test function * Remove uncontactable peers from routing table
1 parent 682b51e commit 994a61b

File tree

5 files changed

+29
-151
lines changed

5 files changed

+29
-151
lines changed

README.md

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,6 @@ A simple example of creating this service is as follows:
8080

8181
# Addresses in ENRs
8282

83-
This protocol will drop messages (i.e not respond to requests) from peers that
84-
advertise non-contactable address in their ENR (e.g `127.0.0.1` when connecting
85-
to non-local nodes). This section
86-
explains the rationale behind this design decision.
87-
8883
An ENR is a signed record which is primarily used in this protocol for
8984
identifying and connecting to peers. ENRs have **OPTIONAL** `ip` and `port`
9085
fields.
@@ -127,8 +122,5 @@ This is done in the following way:
127122
3. If a peer connects to us with an ENR that specifies an IP address that does
128123
not match the src socket it connects to us on (e.g `127.0.0.1`, or
129124
potentially some internal subnet IP that is unreachable from our current
130-
network) we consider this peer malicious/faulty
131-
and drop all packets. This way we can efficiently drop peers that may try to
132-
get us to send messages to arbitrary remote IPs, and we can be sure that all
133-
ENRs in our routing table are contactable (at least by our local node at
134-
some point in time).
125+
network) we consider this ENR invalid and will not add it to our routing
126+
table. However we will still respond to discovery requests.

src/handler/mod.rs

Lines changed: 18 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,6 @@ use session::Session;
7474
// seconds).
7575
const BANNED_NODES_CHECK: u64 = 300; // Check every 5 minutes.
7676

77-
// The one-time session timeout.
78-
const ONE_TIME_SESSION_TIMEOUT: u64 = 30;
79-
80-
// The maximum number of established one-time sessions to maintain.
81-
const ONE_TIME_SESSION_CACHE_CAPACITY: usize = 100;
82-
8377
/// Messages sent from the application layer to `Handler`.
8478
#[derive(Debug, Clone, PartialEq)]
8579
#[allow(clippy::large_enum_variant)]
@@ -216,8 +210,6 @@ pub struct Handler {
216210
active_challenges: HashMapDelay<NodeAddress, Challenge>,
217211
/// Established sessions with peers.
218212
sessions: LruTimeCache<NodeAddress, Session>,
219-
/// Established sessions with peers for a specific request, stored just one per node.
220-
one_time_sessions: LruTimeCache<NodeAddress, (RequestId, Session)>,
221213
/// The channel to receive messages from the application layer.
222214
service_recv: mpsc::UnboundedReceiver<HandlerIn>,
223215
/// The channel to send messages to the application layer.
@@ -308,10 +300,6 @@ impl Handler {
308300
config.session_timeout,
309301
Some(config.session_cache_capacity),
310302
),
311-
one_time_sessions: LruTimeCache::new(
312-
Duration::from_secs(ONE_TIME_SESSION_TIMEOUT),
313-
Some(ONE_TIME_SESSION_CACHE_CAPACITY),
314-
),
315303
active_challenges: HashMapDelay::new(config.request_timeout),
316304
service_recv,
317305
service_send,
@@ -549,9 +537,6 @@ impl Handler {
549537
// Check for an established session
550538
let packet = if let Some(session) = self.sessions.get_mut(&node_address) {
551539
session.encrypt_message::<P>(self.node_id, &response.encode())
552-
} else if let Some(mut session) = self.remove_one_time_session(&node_address, &response.id)
553-
{
554-
session.encrypt_message::<P>(self.node_id, &response.encode())
555540
} else {
556541
// Either the session is being established or has expired. We simply drop the
557542
// response in this case.
@@ -847,7 +832,7 @@ impl Handler {
847832
ephem_pubkey,
848833
enr_record,
849834
) {
850-
Ok((mut session, enr)) => {
835+
Ok((session, enr)) => {
851836
// Remove the expected response for the challenge.
852837
self.remove_expected_response(node_address.socket_addr);
853838
// Receiving an AuthResponse must give us an up-to-date view of the node ENR.
@@ -868,28 +853,17 @@ impl Handler {
868853
{
869854
warn!(error = %e, "Failed to inform of established session")
870855
}
871-
// When (re-)establishing a session from an outgoing challenge, we do not need
872-
// to filter out this request from active requests, so we do not pass
873-
// the message nonce on to `new_session`.
874-
self.new_session::<P>(node_address.clone(), session, None)
875-
.await;
876-
self.handle_message(
877-
node_address.clone(),
878-
message_nonce,
879-
message,
880-
authenticated_data,
881-
)
882-
.await;
883856
} else {
884-
// IP's or NodeAddress don't match. Drop the session.
857+
// IP's or NodeAddress don't match.
858+
//
859+
// We still handle the request, but we do not add the ENR to our routing
860+
// table or consider the ENR valid.
885861
warn!(
886862
udp4_socket = ?enr.udp4_socket(),
887863
udp6_socket = ?enr.udp6_socket(),
888864
expected = %node_address,
889865
"Session has invalid ENR",
890866
);
891-
self.fail_session(&node_address, RequestError::InvalidRemoteEnr, true)
892-
.await;
893867

894868
// The ENR doesn't verify. Notify application.
895869
self.notify_unverifiable_enr(
@@ -898,39 +872,20 @@ impl Handler {
898872
node_address.node_id,
899873
)
900874
.await;
901-
902-
// Respond to PING request even if the ENR or NodeAddress don't match
903-
// so that the source node can notice its external IP address has been changed.
904-
let maybe_ping_request = match session.decrypt_message(
905-
message_nonce,
906-
message,
907-
authenticated_data,
908-
) {
909-
Ok(m) => match Message::decode(&m) {
910-
Ok(Message::Request(request)) if request.msg_type() == 1 => {
911-
Some(request)
912-
}
913-
_ => None,
914-
},
915-
_ => None,
916-
};
917-
if let Some(request) = maybe_ping_request {
918-
debug!(
919-
%node_address,
920-
"Responding to a PING request using a one-time session.",
921-
);
922-
self.one_time_sessions
923-
.insert(node_address.clone(), (request.id.clone(), session));
924-
if let Err(e) = self
925-
.service_send
926-
.send(HandlerOut::Request(node_address.clone(), Box::new(request)))
927-
.await
928-
{
929-
warn!(error = %e, "Failed to report request to application");
930-
self.one_time_sessions.remove(&node_address);
931-
}
932-
}
933875
}
876+
877+
// When (re-)establishing a session from an outgoing challenge, we do not need
878+
// to filter out this request from active requests, so we do not pass
879+
// the message nonce on to `new_session`.
880+
self.new_session::<P>(node_address.clone(), session, None)
881+
.await;
882+
self.handle_message(
883+
node_address.clone(),
884+
message_nonce,
885+
message,
886+
authenticated_data,
887+
)
888+
.await;
934889
}
935890
Err(Error::InvalidChallengeSignature(challenge)) => {
936891
warn!(
@@ -1294,24 +1249,6 @@ impl Handler {
12941249
}
12951250
}
12961251

1297-
/// Remove one-time session by the given NodeAddress and RequestId if exists.
1298-
fn remove_one_time_session(
1299-
&mut self,
1300-
node_address: &NodeAddress,
1301-
request_id: &RequestId,
1302-
) -> Option<Session> {
1303-
match self.one_time_sessions.peek(node_address) {
1304-
Some((id, _)) if id == request_id => {
1305-
let (_, session) = self
1306-
.one_time_sessions
1307-
.remove(node_address)
1308-
.expect("one-time session must exist");
1309-
Some(session)
1310-
}
1311-
_ => None,
1312-
}
1313-
}
1314-
13151252
/// A request has failed.
13161253
async fn fail_request(
13171254
&mut self,

src/handler/session.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -265,11 +265,3 @@ impl Session {
265265
Ok((packet, session))
266266
}
267267
}
268-
269-
#[cfg(test)]
270-
pub(crate) fn build_dummy_session() -> Session {
271-
Session::new(Keys {
272-
encryption_key: [0; 16],
273-
decryption_key: [0; 16],
274-
})
275-
}

src/handler/tests.rs

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ use std::{
1515
ops::Add,
1616
};
1717

18-
use crate::{
19-
handler::{session::build_dummy_session, HandlerOut::RequestFailed},
20-
RequestError::SelfRequest,
21-
};
18+
use crate::{handler::HandlerOut::RequestFailed, RequestError::SelfRequest};
2219
use active_requests::ActiveRequests;
2320
use std::time::Duration;
2421
use tokio::time::sleep;
@@ -78,10 +75,6 @@ async fn build_handler<P: ProtocolIdentity>(
7875
pending_requests: HashMap::new(),
7976
filter_expected_responses,
8077
sessions: LruTimeCache::new(config.session_timeout, Some(config.session_cache_capacity)),
81-
one_time_sessions: LruTimeCache::new(
82-
Duration::from_secs(ONE_TIME_SESSION_TIMEOUT),
83-
Some(ONE_TIME_SESSION_CACHE_CAPACITY),
84-
),
8578
active_challenges: HashMapDelay::new(config.request_timeout),
8679
service_recv,
8780
service_send,
@@ -580,50 +573,6 @@ async fn test_self_request_ipv6() {
580573
);
581574
}
582575

583-
#[tokio::test]
584-
async fn remove_one_time_session() {
585-
let config = ConfigBuilder::new(ListenConfig::default()).build();
586-
let key = CombinedKey::generate_secp256k1();
587-
let enr = Enr::builder()
588-
.ip4(Ipv4Addr::LOCALHOST)
589-
.udp4(9000)
590-
.build(&key)
591-
.unwrap();
592-
let (_, _, _, mut handler) = build_handler::<DefaultProtocolId>(enr, key, config).await;
593-
594-
let enr = {
595-
let key = CombinedKey::generate_secp256k1();
596-
Enr::builder()
597-
.ip4(Ipv4Addr::LOCALHOST)
598-
.udp4(9000)
599-
.build(&key)
600-
.unwrap()
601-
};
602-
let node_address = NodeAddress::new("127.0.0.1:9000".parse().unwrap(), enr.node_id());
603-
let request_id = RequestId::random();
604-
let session = build_dummy_session();
605-
handler
606-
.one_time_sessions
607-
.insert(node_address.clone(), (request_id.clone(), session));
608-
609-
let other_request_id = RequestId::random();
610-
assert!(handler
611-
.remove_one_time_session(&node_address, &other_request_id)
612-
.is_none());
613-
assert_eq!(1, handler.one_time_sessions.len());
614-
615-
let other_node_address = NodeAddress::new("127.0.0.1:9001".parse().unwrap(), enr.node_id());
616-
assert!(handler
617-
.remove_one_time_session(&other_node_address, &request_id)
618-
.is_none());
619-
assert_eq!(1, handler.one_time_sessions.len());
620-
621-
assert!(handler
622-
.remove_one_time_session(&node_address, &request_id)
623-
.is_some());
624-
assert_eq!(0, handler.one_time_sessions.len());
625-
}
626-
627576
// Tests replaying active requests.
628577
//
629578
// In this test, Receiver's session expires and Receiver returns WHOAREYOU.

src/service.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,14 @@ impl Service {
410410
self.rpc_failure(request_id, error);
411411
}
412412
HandlerOut::UnverifiableEnr{enr, socket, node_id} => {
413+
// We have received an ENR that has incorrect socket address fields
414+
// (given the source of the pakcet we received.
415+
// If this node exists in our routing table, remove it, as it may have shifted
416+
// ip addresses and is now no longer contactable.
417+
let key = kbucket::Key::from(node_id);
418+
if self.kbuckets.write().remove(&key) {
419+
debug!(?node_id, "Uncontactable node removed from routing table");
420+
}
413421
self.send_event(Event::UnverifiableEnr{enr, socket, node_id});
414422
}
415423
}

0 commit comments

Comments
 (0)