summaryrefslogtreecommitdiffstats
path: root/src/swarm/peer/admit.rs
blob: 1800775fc83770521294eb3134961a4388079bb0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
//! Admission with a swarm key: only peers that proved it get past `admit`.

use tracing::{debug, info, trace, warn};

use super::handshake::remember_peer;
use crate::state::{AppState, HelloState, Standing};
use crate::transport::ParsedMessage;

/// With a swarm key, only peers that proved it get past this point. A HELLO or
/// HELLO_REPLY must carry a valid tag, and it admits its sender; anything else is only
/// accepted from a peer admitted that way. Everything else is ignored without a reply,
/// so strangers learn nothing, not even whether we know the key.
pub(super) async fn admit(state: &AppState, peer: &str, message: &ParsedMessage) -> bool {
    let Some(key) = &state.swarm_key else {
        return true;
    };
    let local = state.local_id();

    let proof = match message {
        ParsedMessage::Hello(hello) => Some(key.verify_hello(
            &hello.auth,
            hello.swarm_id.as_deref().unwrap_or(""),
            peer,
            local,
        )),
        ParsedMessage::HelloReply(reply) => {
            Some(key.verify_hello_reply(&reply.auth, &reply.swarm_id, peer, local))
        }
        _ => None,
    };

    match proof {
        Some(true) => {
            remember_peer(state, peer).await;
            if let Some(peer_state) = state.get_peer_state(peer).await {
                let mut peer_state = peer_state.lock().await;
                if peer_state.standing == Standing::Stranger {
                    peer_state.standing = Standing::Admitted;
                    debug!(peer = %peer, "Peer proved the swarm key");
                }
            }
            true
        }
        Some(false) => {
            // A known peer with a wrong key (e.g. one that was given another key) loses
            // its admission until it proves the right one, and with it its membership:
            // we stop syncing our document to it, not just listening to it.
            if let Some(peer_state) = state.get_peer_state(peer).await {
                let mut peer_state = peer_state.lock().await;
                if peer_state.standing.is_admitted() {
                    warn!(peer = %peer, "Peer no longer proves the swarm key; dropping its membership");
                }
                peer_state.standing = Standing::Stranger;
                peer_state.hello_state = HelloState::NotSent;
                peer_state.reset_sync();
            }
            // The one place an operator can see why a node can't join: rate limited,
            // since anyone can send us handshakes.
            if let Some(suppressed) = key.note_rejection() {
                info!(
                    peer = %peer,
                    suppressed,
                    "Rejected handshake without a valid swarm key proof"
                );
            }
            false
        }
        None => {
            let admitted = match state.get_peer_state(peer).await {
                Some(peer_state) => peer_state.lock().await.standing.is_admitted(),
                None => false,
            };
            if !admitted {
                trace!(peer = %peer, "Ignoring message from a peer that hasn't proven the swarm key");
            }
            admitted
        }
    }
}