Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Respond to a PING even if the source doesn't match the ENR #184

Merged
merged 19 commits into from
Jun 21, 2023

Conversation

ackintosh
Copy link
Member

@ackintosh ackintosh commented May 19, 2023

Description

Our discv5 implementation drops a request if the source IP address doesn't match the ENR. For PING request, due to this behaviour, the source node can't notice that its external IP address has been changed.

Here is a simulation that reproduce that issue.
ackintosh/discv5-testground#98

This PR makes discv5 to respond PING even if the source IP address doesn't match.

Notes & open questions

one-time sessions

I introduced one-time session at handler to encrypt PONG response without storing a (normal) session. One-time session here is a session for a specific request such that a PING request mentioned above. As the name implies, one-time session is removed once it's used.

New config parameters

Added config parameters for one-time session introduced by this PR.

  • one_time_session_timeout
  • one_time_session_cache_capacity

These are similar to the parameters for normal sessions, but the default values are set to 30 seconds, and 100 respectively.

Simulation test for this PR

Also I have created a Testground test plan that simulates IP changes.
ackintosh/discv5-testground#98

The result is fine. Here is a sequence diagram that illustrates how this change behaves on IP changes.

sequenceDiagram
    participant Node1
    participant Node2 ... Node N

    rect rgb(10, 10, 10)
    Note left of Node1: They communicate with each other<br> to establish a session.
    Note over Node1,Node2 ... Node N: Session established
    end

    rect rgb(10, 10, 10)
    Note left of Node1: Node1 changes its IP address<br> but doesn't update its ENR.
    Note over Node1: *** Change its IP address ***
    end

    Node1 ->> Node2 ... Node N: PING
    Note over Node2 ... Node N: No session with the new IP exists
    Node2 ... Node N ->> Node1: WHOAREYOU
    Note over Node1: Establish new session
    Node1 ->> Node2 ... Node N: Auth message<br> (The ENR record has the old IP)
    Note over Node2 ... Node N: `verify_enr()` results in false<br> as the ENR and src IP don't match.
    Note over Node2 ... Node N: Establish one-time session with Node1's new IP
    
    rect rgb(0, 255, 0)
    Note over Node2 ... Node N: Respond the PING using the one-time session
    end

    Node2 ... Node N ->> Node1: PONG

    rect rgb(0, 255, 0)
    Note over Node1: Update local ENR to the new IP via the IpVote process.
    end

    Node1 ->> Node2 ... Node N: PING
    Note over Node2 ... Node N: No (normal) session with the new IP exists
    Node2 ... Node N ->> Node1: WHOAREYOU
    Note over Node1: Establish new session
    Node1 ->> Node2 ... Node N: Auth message<br> (The ENR record has the new IP)

    Note over Node2 ... Node N: `verify_enr()` results in true here
    rect rgb(0, 255, 0)
    Note over Node2 ... Node N: Establish new (normal) session
    end

    Node2 ... Node N ->> Node1: PONG
Loading

Change checklist

  • Self-review
  • Documentation updates if relevant
  • Tests if relevant

@ackintosh

This comment was marked as resolved.

@ackintosh

This comment was marked as resolved.

Copy link
Collaborator

@divagant-martian divagant-martian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple suggestions about the logic and some about the code. Ty for the great work as always

src/handler/mod.rs Outdated Show resolved Hide resolved
src/handler/mod.rs Outdated Show resolved Hide resolved
src/handler/mod.rs Outdated Show resolved Hide resolved
src/handler/mod.rs Outdated Show resolved Hide resolved
@@ -813,6 +828,37 @@ impl Handler {
// established. If so process them.
self.send_next_request::<P>(node_address).await;
} else {
// Respond to PING request even if the ENR or NodeAddress don't match
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here if we store just one one-time-session per peer, we would want to check first for one such session being already established before doing the decode

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we would want to check first for one such session being already established

You mean we would want to move the second statement of below (else if ... self.remove_one_time_session() ...) to the top of the if statement?

discv5/src/handler/mod.rs

Lines 522 to 535 in fce0fce

// Check for an established session
let packet = if let Some(session) = self.sessions.get_mut(&node_address) {
session.encrypt_message::<P>(self.node_id, &response.encode())
} else if let Some(mut session) = self.remove_one_time_session(&node_address, &response.id)
{
session.encrypt_message::<P>(self.node_id, &response.encode())
} else {
// Either the session is being established or has expired. We simply drop the
// response in this case.
return warn!(
"Session is not established. Dropping response {} for node: {}",
response, node_address.node_id
);
};

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, this is what I mean

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My slight concern about doing that is, the second statement (if ... self.remove_one_time_session() ...) is false in most cases. Establishing a one-time session is under limited conditions, so the result of the second statement should be false in most send_response() calls.

Do you have a good reason for moving the second statement to the first?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reasoning is that is not impossible to have a one-time session created for a peer, then a normal session created, and then hit this line. A bit of a race condition. Moving the check up would prevent this. It's a matter of correctness but I understand your pov as a matter of performance. What are your thoughts on this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point. Your suggestion is right in terms of correctness.

a one-time session created for a peer, then a normal session created, and then hit this line

Yeah it looks a bit of a race condition but I think that if a normal session created, it's fine to use the normal session even if a one-time session (for the request) exists since the normal session is valid as well. The one-time session will be evicted over time without being used.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AgeManning do you have any opinion here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really.
Its a single hash lookup that we are worried about. I think if we have a session, we shouldn't establish a one-time session. But there is a race condition where a session gets created after. So I think its a relatively rare case that we have both sessions. In this case, we probably want to prioritise the normal session over the one-time session.

Seeing as we are probably running this a bit, I agree with @ackintosh in that it's nicer to avoid the hash lookup (i.e check and remove the one-time session) to handle this edge case. It's also no real loss because the one-time sessions only last 30 seconds (assuming they get pruned).

But imo we are debating over a single hash lookup vs correctness, so i'm really on the fence.

Maybe just leave it as @ackintosh has it, because we don't have to do another commit?
I don't feel strongly at all, if either one of you do, happy to go that way.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also of no strong opinion so merged 🤷 seems we are all happy so

@ackintosh ackintosh changed the title [WIP] Respond PING even if the source doesn't match the ENR Respond PING even if the source doesn't match the ENR Jun 14, 2023
@ackintosh ackintosh marked this pull request as ready for review June 15, 2023 22:15
@ackintosh
Copy link
Member Author

@AgeManning @divagant-martian This PR is ready for review now.

Copy link
Collaborator

@divagant-martian divagant-martian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor things. Otherwise letting age finish the review

src/config.rs Outdated
Comment on lines 38 to 43
/// The one-time session timeout. Default: 30 seconds.
pub one_time_session_timeout: Duration,

/// The maximum number of established one-time sessions to maintain. Default: 100.
pub one_time_session_cache_capacity: usize,

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these config values are user-facing, we should probably explain here/somewhere what one time sessions are and what they do

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I'm thinking of moving some more things to be constants in the code. I don't think there are many people changing the configs here. These potentially could be constants, what do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep good idea

Comment on lines 850 to 858
self.one_time_sessions
.insert(node_address.clone(), (request.id.clone(), session));
if let Err(e) = self
.service_send
.send(HandlerOut::Request(node_address, Box::new(request)))
.await
{
warn!("Failed to report request to application {}", e)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will self clean, but if failed to report the request, there is no point in storing the one-time-session

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing it out!

Copy link
Member

@AgeManning AgeManning left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. It should make us a little more friendly to people adjusting their IPs.

src/config.rs Outdated
Comment on lines 38 to 43
/// The one-time session timeout. Default: 30 seconds.
pub one_time_session_timeout: Duration,

/// The maximum number of established one-time sessions to maintain. Default: 100.
pub one_time_session_cache_capacity: usize,

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I'm thinking of moving some more things to be constants in the code. I don't think there are many people changing the configs here. These potentially could be constants, what do you think?

src/handler/mod.rs Outdated Show resolved Hide resolved
Co-authored-by: Age Manning <Age@AgeManning.com>
@ackintosh ackintosh changed the title Respond PING even if the source doesn't match the ENR Respond to a PING even if the source doesn't match the ENR Jun 20, 2023
@ackintosh
Copy link
Member Author

Thank you guys for the review! I have made changes based on the feedback.

@AgeManning Please let us know if you have any thoughts on #184 (comment).

@divagant-martian divagant-martian merged commit 94e09e1 into sigp:master Jun 21, 2023
6 checks passed
@ackintosh ackintosh deleted the pong-even-if-src-not-match branch June 22, 2023 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants