Skip to main content

ckb_network/protocols/identify/
mod.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use ckb_logger::{debug, error, trace, warn};
6use ckb_systemtime::{Duration, Instant};
7use p2p::{
8    SessionId, async_trait,
9    bytes::Bytes,
10    context::{ProtocolContext, ProtocolContextMutRef, SessionContext},
11    multiaddr::{Multiaddr, Protocol},
12    service::TargetProtocol,
13    traits::ServiceProtocol,
14    utils::{extract_peer_id, is_reachable, multiaddr_to_socketaddr},
15};
16
17mod protocol;
18
19use crate::{NetworkState, PeerIdentifyInfo, SupportProtocols, peer_store::required_flags_filter};
20use ckb_types::{packed, prelude::*};
21
22use protocol::IdentifyMessage;
23
24const MAX_RETURN_LISTEN_ADDRS: usize = 10;
25const BAN_ON_NOT_SAME_NET: Duration = Duration::from_secs(5 * 60);
26const CHECK_TIMEOUT_TOKEN: u64 = 100;
27// Check timeout interval (seconds)
28const CHECK_TIMEOUT_INTERVAL: u64 = 1;
29const DEFAULT_TIMEOUT: u64 = 8;
30const MAX_ADDRS: usize = 10;
31
32pub(super) fn is_remote_listen_addr_allowed(addr: &Multiaddr, global_ip_only: bool) -> bool {
33    if let Some(socket_addr) = multiaddr_to_socketaddr(addr) {
34        !global_ip_only || is_reachable(socket_addr.ip())
35    } else {
36        addr.iter()
37            .any(|protocol| matches!(protocol, Protocol::Onion3(_)))
38    }
39}
40
41/// The misbehavior to report to underlying peer storage
42#[allow(dead_code)]
43#[derive(Clone, Debug)]
44pub enum Misbehavior {
45    /// Repeat received message
46    DuplicateReceived,
47    /// Timeout reached
48    Timeout,
49    /// Remote peer send invalid data
50    InvalidData,
51    /// Send too many addresses in listen addresses
52    TooManyAddresses(usize),
53}
54
55/// Misbehavior report result
56pub enum MisbehaveResult {
57    /// Continue to run
58    Continue,
59    /// Disconnect this peer
60    Disconnect,
61}
62
63impl MisbehaveResult {
64    pub fn is_disconnect(&self) -> bool {
65        matches!(self, MisbehaveResult::Disconnect)
66    }
67}
68
69/// The trait to communicate with underlying peer storage
70#[async_trait]
71pub trait Callback: Clone + Send {
72    // Register open protocol
73    fn register(&self, context: &ProtocolContextMutRef, version: &str) -> bool;
74    // remove registered identify protocol
75    fn unregister(&self, context: &ProtocolContextMutRef);
76    /// Received custom message
77    async fn received_identify(
78        &mut self,
79        context: &mut ProtocolContextMutRef<'_>,
80        identify: &[u8],
81    ) -> MisbehaveResult;
82    /// Get custom identify message
83    fn identify(&mut self) -> &[u8];
84    /// Get local listen addresses
85    fn local_listen_addrs(&mut self) -> Vec<Multiaddr>;
86    /// Add remote peer's listen addresses
87    fn add_remote_listen_addrs(&mut self, session: &SessionContext, addrs: Vec<Multiaddr>);
88    /// Add our address observed by remote peer
89    fn add_observed_addr(&mut self, addr: Multiaddr, session_id: SessionId) -> MisbehaveResult;
90    /// Report misbehavior
91    fn misbehave(&mut self, session: &SessionContext, kind: Misbehavior) -> MisbehaveResult;
92}
93
94/// Identify protocol
95pub struct IdentifyProtocol<T> {
96    callback: T,
97    remote_infos: HashMap<SessionId, RemoteInfo>,
98    global_ip_only: bool,
99}
100
101impl<T: Callback> IdentifyProtocol<T> {
102    pub fn new(callback: T) -> IdentifyProtocol<T> {
103        IdentifyProtocol {
104            callback,
105            remote_infos: HashMap::default(),
106            global_ip_only: true,
107        }
108    }
109
110    #[cfg(test)]
111    pub fn global_ip_only(mut self, only: bool) -> Self {
112        self.global_ip_only = only;
113        self
114    }
115
116    fn check_duplicate(&mut self, context: &mut ProtocolContextMutRef) -> MisbehaveResult {
117        let session = context.session;
118        let info = self
119            .remote_infos
120            .get_mut(&session.id)
121            .expect("RemoteInfo must exists");
122
123        if info.has_received {
124            self.callback
125                .misbehave(&info.session, Misbehavior::DuplicateReceived)
126        } else {
127            info.has_received = true;
128            MisbehaveResult::Continue
129        }
130    }
131
132    fn process_listens(
133        &mut self,
134        context: &mut ProtocolContextMutRef,
135        listens: Vec<Multiaddr>,
136    ) -> MisbehaveResult {
137        let session = context.session;
138        let info = self
139            .remote_infos
140            .get_mut(&session.id)
141            .expect("RemoteInfo must exists");
142
143        if listens.len() > MAX_ADDRS {
144            self.callback
145                .misbehave(&info.session, Misbehavior::TooManyAddresses(listens.len()))
146        } else {
147            let global_ip_only = self.global_ip_only;
148            let reachable_addrs = listens
149                .into_iter()
150                .filter(|addr| is_remote_listen_addr_allowed(addr, global_ip_only))
151                .collect::<Vec<_>>();
152            self.callback
153                .add_remote_listen_addrs(session, reachable_addrs);
154            MisbehaveResult::Continue
155        }
156    }
157
158    fn process_observed(
159        &mut self,
160        context: &mut ProtocolContextMutRef,
161        observed: Multiaddr,
162    ) -> MisbehaveResult {
163        debug!(
164            "IdentifyProtocol process observed address, session: {:?}, observed: {}",
165            context.session, observed,
166        );
167
168        let session = context.session;
169        let info = self
170            .remote_infos
171            .get_mut(&session.id)
172            .expect("RemoteInfo must exists");
173        self.callback.add_observed_addr(observed, info.session.id);
174        MisbehaveResult::Continue
175    }
176}
177
178pub(crate) struct RemoteInfo {
179    session: SessionContext,
180    connected_at: Instant,
181    timeout: Duration,
182    has_received: bool,
183}
184
185impl RemoteInfo {
186    fn new(session: SessionContext, timeout: Duration) -> RemoteInfo {
187        RemoteInfo {
188            session,
189            connected_at: Instant::now(),
190            timeout,
191            has_received: false,
192        }
193    }
194}
195
196#[async_trait]
197impl<T: Callback> ServiceProtocol for IdentifyProtocol<T> {
198    async fn init(&mut self, context: &mut ProtocolContext) {
199        let proto_id = context.proto_id;
200        if let Err(err) = context
201            .set_service_notify(
202                proto_id,
203                Duration::from_secs(CHECK_TIMEOUT_INTERVAL),
204                CHECK_TIMEOUT_TOKEN,
205            )
206            .await
207        {
208            error!("IdentifyProtocol init error: {:?}", err)
209        }
210    }
211
212    async fn connected(&mut self, context: ProtocolContextMutRef<'_>, version: &str) {
213        let session = context.session;
214        debug!("IdentifyProtocol connected, session: {:?}", session);
215        let remote_info = RemoteInfo::new(session.clone(), Duration::from_secs(DEFAULT_TIMEOUT));
216        self.remote_infos.insert(session.id, remote_info);
217        let listen_addrs = if self.callback.register(&context, version) {
218            Vec::new()
219        } else {
220            self.callback
221                .local_listen_addrs()
222                .iter()
223                .filter(|addr| {
224                    if let Some(socket_addr) = multiaddr_to_socketaddr(addr) {
225                        !self.global_ip_only || is_reachable(socket_addr.ip())
226                    } else {
227                        // allow /onion3 address
228                        addr.iter()
229                            .any(|protocol| matches!(protocol, Protocol::Onion3(_)))
230                    }
231                })
232                .take(MAX_ADDRS)
233                .cloned()
234                .collect()
235        };
236
237        let identify = self.callback.identify();
238        let data = IdentifyMessage::new(listen_addrs, session.address.clone(), identify).encode();
239        let _ = context
240            .quick_send_message(data)
241            .await
242            .map_err(|err| error!("IdentifyProtocol quick_send_message, error: {:?}", err));
243    }
244
245    async fn disconnected(&mut self, context: ProtocolContextMutRef<'_>) {
246        self.remote_infos
247            .remove(&context.session.id)
248            .expect("RemoteInfo must exists");
249        debug!(
250            "IdentifyProtocol disconnected, session: {:?}",
251            context.session
252        );
253        self.callback.unregister(&context);
254    }
255
256    async fn received(&mut self, mut context: ProtocolContextMutRef<'_>, data: Bytes) {
257        let session = context.session;
258        match IdentifyMessage::decode(&data) {
259            Some(message) => {
260                trace!(
261                    "IdentifyProtocol received, session: {:?}, listen_addrs: {:?}, observed_addr: {}",
262                    context.session, message.listen_addrs, message.observed_addr
263                );
264
265                // Interrupt processing if error, avoid pollution
266                if let MisbehaveResult::Disconnect = self.check_duplicate(&mut context) {
267                    error!(
268                        "Disconnect IdentifyProtocol session {:?} due to duplication.",
269                        session
270                    );
271                    let _ = context.disconnect(session.id).await;
272                    return;
273                }
274                if let MisbehaveResult::Disconnect = self
275                    .callback
276                    .received_identify(&mut context, message.identify)
277                    .await
278                {
279                    error!(
280                        "Disconnect IdentifyProtocol session {:?} due to invalid identify message.",
281                        session,
282                    );
283                    let _ = context.disconnect(session.id).await;
284                    return;
285                }
286                if let MisbehaveResult::Disconnect =
287                    self.process_listens(&mut context, message.listen_addrs.clone())
288                {
289                    error!(
290                        "Disconnect IdentifyProtocol session {:?} due to invalid listen addrs: {:?}.",
291                        session, message.listen_addrs,
292                    );
293                    let _ = context.disconnect(session.id).await;
294                    return;
295                }
296                if let MisbehaveResult::Disconnect =
297                    self.process_observed(&mut context, message.observed_addr.clone())
298                {
299                    error!(
300                        "Disconnect IdentifyProtocol session {:?} due to invalid observed addr: {}.",
301                        session, message.observed_addr,
302                    );
303                    let _ = context.disconnect(session.id).await;
304                }
305            }
306            None => {
307                let info = self
308                    .remote_infos
309                    .get(&session.id)
310                    .expect("RemoteInfo must exists");
311                if self
312                    .callback
313                    .misbehave(&info.session, Misbehavior::InvalidData)
314                    .is_disconnect()
315                {
316                    let _ = context.disconnect(session.id).await;
317                }
318            }
319        }
320    }
321
322    async fn notify(&mut self, context: &mut ProtocolContext, _token: u64) {
323        for (session_id, info) in &self.remote_infos {
324            if !info.has_received && (info.connected_at + info.timeout) <= Instant::now() {
325                let misbehave_result = self.callback.misbehave(&info.session, Misbehavior::Timeout);
326                if misbehave_result.is_disconnect() {
327                    let _ = context.disconnect(*session_id).await;
328                }
329            }
330        }
331    }
332}
333
334#[derive(Clone)]
335pub struct IdentifyCallback {
336    network_state: Arc<NetworkState>,
337    identify: Identify,
338}
339
340impl IdentifyCallback {
341    pub(crate) fn new(
342        network_state: Arc<NetworkState>,
343        name: String,
344        client_version: String,
345        flags: Flags,
346    ) -> IdentifyCallback {
347        IdentifyCallback {
348            network_state,
349            identify: Identify::new(name, flags, client_version),
350        }
351    }
352
353    fn listen_addrs(&self) -> Vec<Multiaddr> {
354        let addrs = self.network_state.public_addrs(MAX_RETURN_LISTEN_ADDRS * 2);
355        addrs
356            .into_iter()
357            .take(MAX_RETURN_LISTEN_ADDRS)
358            .collect::<Vec<_>>()
359    }
360}
361
362#[async_trait]
363impl Callback for IdentifyCallback {
364    fn register(&self, context: &ProtocolContextMutRef, version: &str) -> bool {
365        let session_id = context.session.id;
366        self.network_state.with_peer_registry_mut(|reg| {
367            if let Some(peer) = reg.get_peer_mut(session_id) {
368                peer.protocols.insert(context.proto_id, version.to_owned());
369            }
370            reg.is_anchor(session_id)
371        })
372    }
373
374    fn unregister(&self, context: &ProtocolContextMutRef) {
375        if context.session.ty.is_outbound() {
376            // Due to the filtering strategy of the peer store, if the node is
377            // disconnected after a long connection is maintained for more than seven days,
378            // it is possible that the node will be accidentally evicted, so it is necessary
379            // to reset the last_connected_time of the node when disconnected.
380            self.network_state.with_peer_store_mut(|peer_store| {
381                peer_store.update_outbound_addr_last_connected_ms(context.session.address.clone());
382            });
383        }
384    }
385
386    fn identify(&mut self) -> &[u8] {
387        self.identify.encode()
388    }
389
390    async fn received_identify(
391        &mut self,
392        context: &mut ProtocolContextMutRef<'_>,
393        identify: &[u8],
394    ) -> MisbehaveResult {
395        match self.identify.verify(identify) {
396            None => {
397                self.network_state.ban_session(
398                    &context.control().clone().into(),
399                    context.session.id,
400                    BAN_ON_NOT_SAME_NET,
401                    "The nodes are not on the same network".to_string(),
402                );
403                MisbehaveResult::Disconnect
404            }
405            Some((flags, client_version)) => {
406                let registry_client_version = |version: String| {
407                    self.network_state.with_peer_registry_mut(|registry| {
408                        if let Some(peer) = registry.get_peer_mut(context.session.id) {
409                            peer.identify_info = Some(PeerIdentifyInfo {
410                                client_version: version,
411                                flags,
412                            })
413                        }
414                    });
415                };
416
417                registry_client_version(client_version);
418
419                let required_flags = self.network_state.required_flags;
420
421                if context.session.ty.is_outbound() {
422                    // why don't set inbound here?
423                    // because inbound address can't feeler during staying connected
424                    // and if set it to peer store, it will be broadcast to the entire network,
425                    // but this is an unverified address
426
427                    self.network_state.with_peer_store_mut(|peer_store| {
428                        peer_store.add_outbound_addr(context.session.address.clone(), flags);
429                    });
430
431                    if self.network_state.with_peer_registry_mut(|reg| {
432                        reg.change_feeler_flags(&context.session.address, flags)
433                    }) {
434                        let _ = context
435                            .open_protocols(
436                                context.session.id,
437                                TargetProtocol::Single(SupportProtocols::Feeler.protocol_id()),
438                            )
439                            .await;
440                    } else if required_flags_filter(required_flags, flags) {
441                        // The remote end can support all local protocols.
442                        let _ = context
443                            .open_protocols(
444                                context.session.id,
445                                TargetProtocol::Filter(Box::new(move |id| {
446                                    id != &SupportProtocols::Feeler.protocol_id()
447                                })),
448                            )
449                            .await;
450                    } else {
451                        // The remote end cannot support all local protocols.
452                        warn!(
453                            "Session closed from IdentifyProtocol due to peer's flag not meeting the requirements"
454                        );
455                        return MisbehaveResult::Disconnect;
456                    }
457                }
458                MisbehaveResult::Continue
459            }
460        }
461    }
462
463    /// Get local listen addresses
464    fn local_listen_addrs(&mut self) -> Vec<Multiaddr> {
465        let mut listens = self.listen_addrs();
466
467        if listens.len() < MAX_RETURN_LISTEN_ADDRS {
468            let observe_addrs = self
469                .network_state
470                .observed_addrs(MAX_RETURN_LISTEN_ADDRS - listens.len());
471            listens.extend(observe_addrs);
472            listens
473        } else {
474            listens
475        }
476    }
477
478    fn add_remote_listen_addrs(&mut self, session: &SessionContext, addrs: Vec<Multiaddr>) {
479        trace!(
480            "IdentifyProtocol add remote listening addresses, session: {:?}, addresses : {:?}",
481            session, addrs,
482        );
483        let flags = self.network_state.with_peer_registry_mut(|reg| {
484            if let Some(peer) = reg.get_peer_mut(session.id) {
485                peer.listened_addrs = addrs.clone();
486                peer.identify_info
487                    .as_ref()
488                    .map(|a| a.flags)
489                    .unwrap_or(Flags::COMPATIBILITY)
490            } else {
491                Flags::COMPATIBILITY
492            }
493        });
494        self.network_state.with_peer_store_mut(|peer_store| {
495            for addr in addrs {
496                if let Err(err) = peer_store.add_addr(addr.clone(), flags) {
497                    error!("IdentifyProtocol failed to add address to peer store, address: {}, error: {:?}", addr, err);
498                }
499            }
500        })
501    }
502
503    fn add_observed_addr(&mut self, mut addr: Multiaddr, session_id: SessionId) -> MisbehaveResult {
504        if extract_peer_id(&addr).is_none() {
505            addr.push(Protocol::P2P(Cow::Borrowed(
506                self.network_state.local_peer_id().as_bytes(),
507            )))
508        }
509
510        self.network_state.add_observed_addr(session_id, addr);
511        // NOTE: for future usage
512        MisbehaveResult::Continue
513    }
514
515    fn misbehave(&mut self, session: &SessionContext, reason: Misbehavior) -> MisbehaveResult {
516        error!(
517            "IdentifyProtocol detects abnormal behavior, session: {:?}, reason: {:?}",
518            session, reason
519        );
520        MisbehaveResult::Disconnect
521    }
522}
523
524#[derive(Clone)]
525struct Identify {
526    name: String,
527    encode_data: ckb_types::bytes::Bytes,
528}
529
530impl Identify {
531    fn new(name: String, flags: Flags, client_version: String) -> Self {
532        Identify {
533            encode_data: packed::Identify::new_builder()
534                .name(name.as_str())
535                .flag(flags.bits())
536                .client_version(client_version.as_str())
537                .build()
538                .as_bytes(),
539            name,
540        }
541    }
542
543    fn encode(&mut self) -> &[u8] {
544        &self.encode_data
545    }
546
547    fn verify(&self, data: &[u8]) -> Option<(Flags, String)> {
548        let reader = packed::IdentifyReader::from_slice(data).ok()?;
549
550        let name = reader.name().as_utf8().ok()?.to_owned();
551        if self.name != name {
552            warn!(
553                "IdentifyProtocol detects peer has different network identifiers, local network id: {}, remote network id: {}",
554                self.name, name,
555            );
556            return None;
557        }
558
559        let flag: u64 = reader.flag().into();
560        if flag == 0 {
561            return None;
562        }
563
564        let raw_client_version = reader.client_version().as_utf8().ok()?.to_owned();
565
566        Some((Flags::from_bits_truncate(flag), raw_client_version))
567    }
568}
569
570bitflags::bitflags! {
571    /// Node Function Identification
572    #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
573    pub struct Flags: u64 {
574        /// Compatibility reserved
575        const COMPATIBILITY = 0b1;
576        /// Discovery protocol, which can provide peers data service
577        const DISCOVERY = 0b10;
578        /// Sync protocol can provide Block and Header download service
579        const SYNC = 0b100;
580        /// Relay protocol, which can provide CompactBlock and Transaction broadcast/forwarding services
581        const RELAY = 0b1000;
582        /// Light client protocol, which can provide Block / Transaction data and existence-proof services
583        const LIGHT_CLIENT = 0b10000;
584        /// Client-side block filter protocol can provide BlockFilter download service
585        const BLOCK_FILTER = 0b100000;
586    }
587}
588
589#[cfg(test)]
590mod tests {
591    use super::is_remote_listen_addr_allowed;
592    use p2p::multiaddr::Multiaddr;
593
594    #[test]
595    fn test_identify_rejects_dns_loopback_listen_addr() {
596        let addr: Multiaddr = format!(
597            "/dns4/localhost/tcp/{}/p2p/{}",
598            rand::random::<u16>(),
599            crate::PeerId::random().to_base58()
600        )
601        .parse()
602        .unwrap();
603
604        assert!(!is_remote_listen_addr_allowed(&addr, true));
605        assert!(!is_remote_listen_addr_allowed(&addr, false));
606    }
607
608    #[test]
609    fn test_identify_remote_listen_addr_allows_socket_addrs_by_policy() {
610        let global_addr: Multiaddr = format!(
611            "/ip4/8.8.8.8/tcp/{}/p2p/{}",
612            rand::random::<u16>(),
613            crate::PeerId::random().to_base58()
614        )
615        .parse()
616        .unwrap();
617        let loopback_addr: Multiaddr = format!(
618            "/ip4/127.0.0.1/tcp/{}/p2p/{}",
619            rand::random::<u16>(),
620            crate::PeerId::random().to_base58()
621        )
622        .parse()
623        .unwrap();
624
625        assert!(is_remote_listen_addr_allowed(&global_addr, true));
626        assert!(!is_remote_listen_addr_allowed(&loopback_addr, true));
627        assert!(is_remote_listen_addr_allowed(&loopback_addr, false));
628    }
629
630    #[test]
631    fn test_identify_remote_listen_addr_allows_onion3() {
632        let onion_addr: Multiaddr =
633            "/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234"
634                .parse()
635                .unwrap();
636
637        assert!(is_remote_listen_addr_allowed(&onion_addr, true));
638        assert!(is_remote_listen_addr_allowed(&onion_addr, false));
639    }
640}