Skip to content

Commit 94e09e1

Browse files
Respond to a PING even if the source doesn't match the ENR (#184)
* Extract ping request from message * Respond PING request using one-time session * cargo fmt * Fix typo * Avoid macros for readability * Move the session failure up so that we don't send a request with a session technically still available * Store one session per node * Tweak comment * Fix clippy warnings * Don't update the time when access one-time sessions * Add parameters for one-time session * Add test for remove_one_time_session * Update src/handler/mod.rs Co-authored-by: Age Manning <[email protected]> * Move the one-time session related configs to be constants * Remove the one-time-session if failed to report the request * Fix tests --------- Co-authored-by: Age Manning <[email protected]>
1 parent 47844ca commit 94e09e1

File tree

3 files changed

+183
-13
lines changed

3 files changed

+183
-13
lines changed

src/handler/mod.rs

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

76+
// The one-time session timeout.
77+
const ONE_TIME_SESSION_TIMEOUT: u64 = 30;
78+
79+
// The maximum number of established one-time sessions to maintain.
80+
const ONE_TIME_SESSION_CACHE_CAPACITY: usize = 100;
81+
7682
/// Messages sent from the application layer to `Handler`.
7783
#[derive(Debug, Clone, PartialEq)]
7884
#[allow(clippy::large_enum_variant)]
@@ -191,6 +197,8 @@ pub struct Handler {
191197
active_challenges: HashMapDelay<NodeAddress, Challenge>,
192198
/// Established sessions with peers.
193199
sessions: LruTimeCache<NodeAddress, Session>,
200+
/// Established sessions with peers for a specific request, stored just one per node.
201+
one_time_sessions: LruTimeCache<NodeAddress, (RequestId, Session)>,
194202
/// The channel to receive messages from the application layer.
195203
service_recv: mpsc::UnboundedReceiver<HandlerIn>,
196204
/// The channel to send messages to the application layer.
@@ -281,6 +289,10 @@ impl Handler {
281289
config.session_timeout,
282290
Some(config.session_cache_capacity),
283291
),
292+
one_time_sessions: LruTimeCache::new(
293+
Duration::from_secs(ONE_TIME_SESSION_TIMEOUT),
294+
Some(ONE_TIME_SESSION_CACHE_CAPACITY),
295+
),
284296
active_challenges: HashMapDelay::new(config.request_timeout),
285297
service_recv,
286298
service_send,
@@ -516,23 +528,23 @@ impl Handler {
516528
response: Response,
517529
) {
518530
// Check for an established session
519-
if let Some(session) = self.sessions.get_mut(&node_address) {
520-
// Encrypt the message and send
521-
let packet = match session.encrypt_message::<P>(self.node_id, &response.encode()) {
522-
Ok(packet) => packet,
523-
Err(e) => {
524-
warn!("Could not encrypt response: {:?}", e);
525-
return;
526-
}
527-
};
528-
self.send(node_address, packet).await;
531+
let packet = if let Some(session) = self.sessions.get_mut(&node_address) {
532+
session.encrypt_message::<P>(self.node_id, &response.encode())
533+
} else if let Some(mut session) = self.remove_one_time_session(&node_address, &response.id)
534+
{
535+
session.encrypt_message::<P>(self.node_id, &response.encode())
529536
} else {
530537
// Either the session is being established or has expired. We simply drop the
531538
// response in this case.
532-
warn!(
539+
return warn!(
533540
"Session is not established. Dropping response {} for node: {}",
534541
response, node_address.node_id
535542
);
543+
};
544+
545+
match packet {
546+
Ok(packet) => self.send(node_address, packet).await,
547+
Err(e) => warn!("Could not encrypt response: {:?}", e),
536548
}
537549
}
538550

@@ -780,7 +792,7 @@ impl Handler {
780792
ephem_pubkey,
781793
enr_record,
782794
) {
783-
Ok((session, enr)) => {
795+
Ok((mut session, enr)) => {
784796
// Receiving an AuthResponse must give us an up-to-date view of the node ENR.
785797
// Verify the ENR is valid
786798
if self.verify_enr(&enr, &node_address) {
@@ -820,6 +832,38 @@ impl Handler {
820832
);
821833
self.fail_session(&node_address, RequestError::InvalidRemoteEnr, true)
822834
.await;
835+
836+
// Respond to PING request even if the ENR or NodeAddress don't match
837+
// so that the source node can notice its external IP address has been changed.
838+
let maybe_ping_request = match session.decrypt_message(
839+
message_nonce,
840+
message,
841+
authenticated_data,
842+
) {
843+
Ok(m) => match Message::decode(&m) {
844+
Ok(Message::Request(request)) if request.msg_type() == 1 => {
845+
Some(request)
846+
}
847+
_ => None,
848+
},
849+
_ => None,
850+
};
851+
if let Some(request) = maybe_ping_request {
852+
debug!(
853+
"Responding to a PING request using a one-time session. node_address: {}",
854+
node_address
855+
);
856+
self.one_time_sessions
857+
.insert(node_address.clone(), (request.id.clone(), session));
858+
if let Err(e) = self
859+
.service_send
860+
.send(HandlerOut::Request(node_address.clone(), Box::new(request)))
861+
.await
862+
{
863+
warn!("Failed to report request to application {}", e);
864+
self.one_time_sessions.remove(&node_address);
865+
}
866+
}
823867
}
824868
}
825869
Err(Discv5Error::InvalidChallengeSignature(challenge)) => {
@@ -1119,6 +1163,24 @@ impl Handler {
11191163
}
11201164
}
11211165

1166+
/// Remove one-time session by the given NodeAddress and RequestId if exists.
1167+
fn remove_one_time_session(
1168+
&mut self,
1169+
node_address: &NodeAddress,
1170+
request_id: &RequestId,
1171+
) -> Option<Session> {
1172+
match self.one_time_sessions.peek(node_address) {
1173+
Some((id, _)) if id == request_id => {
1174+
let (_, session) = self
1175+
.one_time_sessions
1176+
.remove(node_address)
1177+
.expect("one-time session must exist");
1178+
Some(session)
1179+
}
1180+
_ => None,
1181+
}
1182+
}
1183+
11221184
/// A request has failed.
11231185
async fn fail_request(
11241186
&mut self,

src/handler/session.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,11 @@ 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: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ use crate::{
99
};
1010
use std::net::{Ipv4Addr, Ipv6Addr};
1111

12-
use crate::{handler::HandlerOut::RequestFailed, RequestError::SelfRequest};
12+
use crate::{
13+
handler::{session::build_dummy_session, HandlerOut::RequestFailed},
14+
RequestError::SelfRequest,
15+
};
1316
use active_requests::ActiveRequests;
1417
use enr::EnrBuilder;
1518
use std::time::Duration;
@@ -21,6 +24,66 @@ fn init() {
2124
.try_init();
2225
}
2326

27+
async fn build_handler<P: ProtocolIdentity>() -> Handler {
28+
let config = Discv5ConfigBuilder::new(ListenConfig::default()).build();
29+
let key = CombinedKey::generate_secp256k1();
30+
let enr = EnrBuilder::new("v4")
31+
.ip4(Ipv4Addr::LOCALHOST)
32+
.udp4(9000)
33+
.build(&key)
34+
.unwrap();
35+
let mut listen_sockets = SmallVec::default();
36+
listen_sockets.push((Ipv4Addr::LOCALHOST, 9000).into());
37+
let node_id = enr.node_id();
38+
let filter_expected_responses = Arc::new(RwLock::new(HashMap::new()));
39+
40+
let socket = {
41+
let socket_config = {
42+
let filter_config = FilterConfig {
43+
enabled: config.enable_packet_filter,
44+
rate_limiter: config.filter_rate_limiter.clone(),
45+
max_nodes_per_ip: config.filter_max_nodes_per_ip,
46+
max_bans_per_ip: config.filter_max_bans_per_ip,
47+
};
48+
49+
socket::SocketConfig {
50+
executor: config.executor.clone().expect("Executor must exist"),
51+
filter_config,
52+
listen_config: config.listen_config.clone(),
53+
local_node_id: node_id,
54+
expected_responses: filter_expected_responses.clone(),
55+
ban_duration: config.ban_duration,
56+
}
57+
};
58+
59+
Socket::new::<P>(socket_config).await.unwrap()
60+
};
61+
let (_, service_recv) = mpsc::unbounded_channel();
62+
let (service_send, _) = mpsc::channel(50);
63+
let (_, exit) = oneshot::channel();
64+
65+
Handler {
66+
request_retries: config.request_retries,
67+
node_id,
68+
enr: Arc::new(RwLock::new(enr)),
69+
key: Arc::new(RwLock::new(key)),
70+
active_requests: ActiveRequests::new(config.request_timeout),
71+
pending_requests: HashMap::new(),
72+
filter_expected_responses,
73+
sessions: LruTimeCache::new(config.session_timeout, Some(config.session_cache_capacity)),
74+
one_time_sessions: LruTimeCache::new(
75+
Duration::from_secs(ONE_TIME_SESSION_TIMEOUT),
76+
Some(ONE_TIME_SESSION_CACHE_CAPACITY),
77+
),
78+
active_challenges: HashMapDelay::new(config.request_timeout),
79+
service_recv,
80+
service_send,
81+
listen_sockets,
82+
socket,
83+
exit,
84+
}
85+
}
86+
2487
macro_rules! arc_rw {
2588
( $x: expr ) => {
2689
Arc::new(RwLock::new($x))
@@ -353,3 +416,40 @@ async fn test_self_request_ipv6() {
353416
handler_out
354417
);
355418
}
419+
420+
#[tokio::test]
421+
async fn remove_one_time_session() {
422+
let mut handler = build_handler::<DefaultProtocolId>().await;
423+
424+
let enr = {
425+
let key = CombinedKey::generate_secp256k1();
426+
EnrBuilder::new("v4")
427+
.ip4(Ipv4Addr::LOCALHOST)
428+
.udp4(9000)
429+
.build(&key)
430+
.unwrap()
431+
};
432+
let node_address = NodeAddress::new("127.0.0.1:9000".parse().unwrap(), enr.node_id());
433+
let request_id = RequestId::random();
434+
let session = build_dummy_session();
435+
handler
436+
.one_time_sessions
437+
.insert(node_address.clone(), (request_id.clone(), session));
438+
439+
let other_request_id = RequestId::random();
440+
assert!(handler
441+
.remove_one_time_session(&node_address, &other_request_id)
442+
.is_none());
443+
assert_eq!(1, handler.one_time_sessions.len());
444+
445+
let other_node_address = NodeAddress::new("127.0.0.1:9001".parse().unwrap(), enr.node_id());
446+
assert!(handler
447+
.remove_one_time_session(&other_node_address, &request_id)
448+
.is_none());
449+
assert_eq!(1, handler.one_time_sessions.len());
450+
451+
assert!(handler
452+
.remove_one_time_session(&node_address, &request_id)
453+
.is_some());
454+
assert_eq!(0, handler.one_time_sessions.len());
455+
}

0 commit comments

Comments
 (0)