Skip to main content

karyon_p2p/
listener.rs

1use std::{future::Future, marker::PhantomData, sync::Arc, time::Duration};
2
3use log::{debug, error, info};
4
5use karyon_core::{
6    async_runtime::Executor,
7    async_util::{timeout, TaskGroup, TaskResult},
8    crypto::KeyPair,
9};
10use karyon_net::{
11    codec::Codec,
12    framed,
13    tcp::TcpListener,
14    tls::{ServerTlsConfig, TlsLayer},
15    ByteBuffer, ByteStream, Endpoint, FramedConn, ServerLayer,
16};
17
18use crate::{
19    access_control::{AccessControl, Action, Subject},
20    codec::PeerNetMsgCodec,
21    conn_queue::ConnQueue,
22    monitor::{ConnectionKind, Monitor},
23    peer::ConnDirection,
24    slots::ConnectionSlots,
25    tls_config::{peer_id_from_certs, tls_server_config},
26    Error, PeerID, Result,
27};
28
29/// Listener for byte-stream transports (TCP, TLS). Accepting is split in
30/// two phases: `accept` does only the cheap kernel accept, `handshake`
31/// runs the TLS upgrade. QUIC uses a separate `StreamMux` path.
32enum StreamListener {
33    Tcp(TcpListener),
34    Tls(TcpListener, Box<TlsLayer>),
35}
36
37impl StreamListener {
38    /// Kernel accept only. Returns the raw stream before any handshake.
39    async fn accept(&self) -> Result<Box<dyn ByteStream>> {
40        match self {
41            Self::Tcp(l) | Self::Tls(l, _) => Ok(l.accept().await?),
42        }
43    }
44
45    /// TLS upgrade, if this is a TLS listener. Runs off the accept loop.
46    async fn handshake(
47        &self,
48        stream: Box<dyn ByteStream>,
49        handshake_timeout: Duration,
50    ) -> Result<Box<dyn ByteStream>> {
51        match self {
52            Self::Tcp(_) => Ok(stream),
53            Self::Tls(_, layer) => Ok(timeout(
54                handshake_timeout,
55                ServerLayer::handshake(layer.as_ref(), stream),
56            )
57            .await??),
58        }
59    }
60
61    fn local_endpoint(&self) -> Result<Endpoint> {
62        match self {
63            Self::Tcp(l) => Ok(l.local_endpoint()?),
64            // The listener is plain TCP; report the TLS scheme so peers
65            // dialling this endpoint run the handshake.
66            Self::Tls(l, _) => {
67                let ep = l.local_endpoint()?;
68                Ok(Endpoint::Tls(ep.addr()?, ep.port()?))
69            }
70        }
71    }
72}
73
74#[cfg(feature = "quic")]
75use karyon_net::{quic, StreamMux};
76
77/// Creates inbound connections with other peers. Generic over the
78/// codec applied to the framed accepted streams so the same accept
79/// machinery serves the peer data-plane (`PeerNetMsgCodec`) and the
80/// kademlia lookup-plane (`KadNetMsgCodec`).
81pub struct Listener<C: Codec<ByteBuffer> + Default + Clone> {
82    key_pair: KeyPair,
83    task_group: TaskGroup,
84    connection_slots: Arc<ConnectionSlots>,
85    conn_queue: Option<Arc<ConnQueue>>,
86    monitor: Arc<Monitor>,
87    handshake_timeout: Duration,
88    access_control: Arc<dyn AccessControl>,
89    _codec: PhantomData<C>,
90}
91
92impl<C> Listener<C>
93where
94    C: Codec<ByteBuffer, Error = karyon_net::Error> + Default + Clone + Send + Sync + 'static,
95{
96    /// Create a new Listener (no auto-queue; use `start_with_callback`).
97    /// `handshake_timeout` is in seconds.
98    pub fn new(
99        key_pair: &KeyPair,
100        connection_slots: Arc<ConnectionSlots>,
101        monitor: Arc<Monitor>,
102        handshake_timeout: u64,
103        access_control: Arc<dyn AccessControl>,
104        ex: Executor,
105    ) -> Arc<Self> {
106        Arc::new(Self {
107            key_pair: key_pair.clone(),
108            connection_slots,
109            conn_queue: None,
110            task_group: TaskGroup::with_executor(ex),
111            monitor,
112            handshake_timeout: Duration::from_secs(handshake_timeout),
113            access_control,
114            _codec: PhantomData,
115        })
116    }
117
118    /// Start with a user-provided callback for each connection.
119    pub async fn start_with_callback<Fut>(
120        self: &Arc<Self>,
121        endpoint: Endpoint,
122        callback: impl FnOnce(FramedConn<C>) -> Fut + Clone + Send + 'static,
123    ) -> Result<Endpoint>
124    where
125        Fut: Future<Output = Result<()>> + Send + 'static,
126    {
127        let listener = match self.listen(&endpoint).await {
128            Ok(l) => {
129                self.monitor
130                    .notify(ConnectionKind::Listening(endpoint.clone()))
131                    .await;
132                l
133            }
134            Err(err) => {
135                error!("Failed to listen on {endpoint}: {err}");
136                self.monitor
137                    .notify(ConnectionKind::ListenFailed(endpoint))
138                    .await;
139                return Err(err);
140            }
141        };
142
143        let resolved = listener.local_endpoint()?;
144        info!("Start listening on {resolved}");
145
146        self.task_group.spawn_then(
147            {
148                let this = self.clone();
149                async move {
150                    this.listen_loop_callback(Arc::new(listener), callback)
151                        .await
152                }
153            },
154            |res: TaskResult<()>| async move {
155                debug!("Listener callback loop ended: {res}");
156            },
157        );
158        Ok(resolved)
159    }
160
161    pub async fn shutdown(&self) {
162        self.task_group.cancel().await;
163    }
164
165    /// Runs the handshake and frames the stream. Called off the accept
166    /// loop. Also returns the peer id proved by the TLS client cert.
167    async fn upgrade(
168        &self,
169        listener: &StreamListener,
170        stream: Box<dyn ByteStream>,
171    ) -> Result<(FramedConn<C>, Option<PeerID>)> {
172        let stream = listener.handshake(stream, self.handshake_timeout).await?;
173        // Extract the peer cert before framing consumes the stream.
174        let vpid = stream
175            .peer_certificates()
176            .as_deref()
177            .and_then(peer_id_from_certs);
178        Ok((framed(stream, C::default()), vpid))
179    }
180
181    /// Accept loop (callback mode).
182    async fn listen_loop_callback<Fut>(
183        self: Arc<Self>,
184        listener: Arc<StreamListener>,
185        callback: impl FnOnce(FramedConn<C>) -> Fut + Clone + Send + 'static,
186    ) where
187        Fut: Future<Output = Result<()>> + Send + 'static,
188    {
189        loop {
190            self.connection_slots.wait_for_slot().await;
191
192            let stream = match listener.accept().await {
193                Ok(s) => s,
194                Err(err) => {
195                    error!("Failed to accept connection: {err}");
196                    self.monitor.notify(ConnectionKind::AcceptFailed).await;
197                    continue;
198                }
199            };
200
201            let endpoint = match stream.peer_endpoint() {
202                Some(ep) => ep,
203                None => {
204                    self.monitor.notify(ConnectionKind::AcceptFailed).await;
205                    error!("Failed to get peer endpoint");
206                    continue;
207                }
208            };
209
210            // Callback mode is the kademlia lookup plane.
211            if !self.access_control.allow(
212                &Subject::Endpoint(&endpoint),
213                Action::Lookup(ConnDirection::Inbound),
214            ) {
215                debug!("Rejected inbound lookup connection from {endpoint}");
216                continue;
217            }
218
219            self.monitor
220                .notify(ConnectionKind::Accepted(endpoint.clone()))
221                .await;
222            // Counted here so `wait_for_slot` keeps throttling. The task
223            // releases it again, including when the handshake fails.
224            self.connection_slots.add();
225
226            let on_disconnect = {
227                let this = self.clone();
228                |res| async move {
229                    if let TaskResult::Completed(Err(err)) = res {
230                        debug!("Inbound connection dropped: {err}");
231                    }
232                    this.monitor
233                        .notify(ConnectionKind::Disconnected(endpoint))
234                        .await;
235                    this.connection_slots.remove().await;
236                }
237            };
238
239            let this = self.clone();
240            let listener = listener.clone();
241            let callback = callback.clone();
242            self.task_group.spawn_then(
243                async move {
244                    let (conn, _) = this.upgrade(&listener, stream).await?;
245                    callback(conn).await
246                },
247                on_disconnect,
248            );
249        }
250    }
251
252    /// Create a listener for TCP/TLS.
253    async fn listen(&self, endpoint: &Endpoint) -> Result<StreamListener> {
254        match endpoint {
255            Endpoint::Tcp(..) => {
256                let listener = TcpListener::bind(endpoint, Default::default()).await?;
257                Ok(StreamListener::Tcp(listener))
258            }
259            Endpoint::Tls(..) => {
260                let tls_config = ServerTlsConfig {
261                    server_config: tls_server_config(&self.key_pair)?,
262                };
263                let tcp_listener = TcpListener::bind(endpoint, Default::default()).await?;
264                Ok(StreamListener::Tls(
265                    tcp_listener,
266                    Box::new(TlsLayer::server(tls_config)),
267                ))
268            }
269            _ => Err(Error::UnsupportedEndpoint(endpoint.to_string())),
270        }
271    }
272}
273
274// Auto-queue paths only live on the peer-plane Listener. The kademlia
275// lookup plane uses `start_with_callback` and handles each connection
276// inline (no ConnQueue / handshake pipeline).
277impl Listener<PeerNetMsgCodec> {
278    /// Create a new Listener with a ConnQueue (auto-queue mode).
279    /// `handshake_timeout` is in seconds.
280    pub fn new_with_queue(
281        key_pair: &KeyPair,
282        connection_slots: Arc<ConnectionSlots>,
283        conn_queue: Arc<ConnQueue>,
284        monitor: Arc<Monitor>,
285        handshake_timeout: u64,
286        access_control: Arc<dyn AccessControl>,
287        ex: Executor,
288    ) -> Arc<Self> {
289        Arc::new(Self {
290            key_pair: key_pair.clone(),
291            connection_slots,
292            conn_queue: Some(conn_queue),
293            task_group: TaskGroup::with_executor(ex),
294            monitor,
295            handshake_timeout: Duration::from_secs(handshake_timeout),
296            access_control,
297            _codec: PhantomData,
298        })
299    }
300
301    /// Start listening (auto-queue mode). Returns the resolved endpoint.
302    pub async fn start(self: &Arc<Self>, endpoint: Endpoint) -> Result<Endpoint> {
303        #[cfg(feature = "quic")]
304        if endpoint.is_quic() {
305            return self.start_quic(endpoint).await;
306        }
307
308        let listener = match self.listen(&endpoint).await {
309            Ok(l) => {
310                self.monitor
311                    .notify(ConnectionKind::Listening(endpoint.clone()))
312                    .await;
313                l
314            }
315            Err(err) => {
316                error!("Failed to listen on {endpoint}: {err}");
317                self.monitor
318                    .notify(ConnectionKind::ListenFailed(endpoint))
319                    .await;
320                return Err(err);
321            }
322        };
323
324        let resolved = listener.local_endpoint()?;
325        info!("Start listening on {resolved}");
326
327        self.task_group.spawn({
328            let this = self.clone();
329            async move { this.listen_loop(Arc::new(listener)).await }
330        });
331        Ok(resolved)
332    }
333
334    /// Accept loop (auto-queue mode).
335    async fn listen_loop(self: Arc<Self>, listener: Arc<StreamListener>) {
336        let conn_queue = self
337            .conn_queue
338            .as_ref()
339            .expect("listen_loop requires ConnQueue")
340            .clone();
341
342        loop {
343            self.connection_slots.wait_for_slot().await;
344
345            let stream = match listener.accept().await {
346                Ok(s) => s,
347                Err(err) => {
348                    error!("Failed to accept connection: {err}");
349                    self.monitor.notify(ConnectionKind::AcceptFailed).await;
350                    continue;
351                }
352            };
353
354            let endpoint = match stream.peer_endpoint() {
355                Some(ep) => ep,
356                None => {
357                    self.monitor.notify(ConnectionKind::AcceptFailed).await;
358                    error!("Failed to get peer endpoint");
359                    continue;
360                }
361            };
362
363            if !self.access_control.allow(
364                &Subject::Endpoint(&endpoint),
365                Action::Connect(ConnDirection::Inbound),
366            ) {
367                debug!("Rejected inbound connection from {endpoint}");
368                continue;
369            }
370
371            self.monitor
372                .notify(ConnectionKind::Accepted(endpoint.clone()))
373                .await;
374
375            self.connection_slots.add();
376
377            let on_disconnect = {
378                let this = self.clone();
379                |res: TaskResult<Result<()>>| async move {
380                    if let TaskResult::Completed(Err(err)) = res {
381                        debug!("Inbound connection dropped: {err}");
382                    }
383                    this.monitor
384                        .notify(ConnectionKind::Disconnected(endpoint))
385                        .await;
386                    this.connection_slots.remove().await;
387                }
388            };
389
390            let cq = conn_queue.clone();
391            let this = self.clone();
392            let listener = listener.clone();
393            self.task_group.spawn_then(
394                async move {
395                    let (conn, vpid) = this.upgrade(&listener, stream).await?;
396                    cq.handle(conn, ConnDirection::Inbound, vpid).await?;
397                    Ok(())
398                },
399                on_disconnect,
400            );
401        }
402    }
403
404    /// QUIC listener.
405    #[cfg(feature = "quic")]
406    async fn start_quic(self: &Arc<Self>, endpoint: Endpoint) -> Result<Endpoint> {
407        let rustls_config = tls_server_config(&self.key_pair)?;
408        let server_config = quic::ServerQuicConfig::from_rustls(rustls_config);
409
410        let quic_endpoint = match quic::QuicEndpoint::listen(&endpoint, server_config).await {
411            Ok(ep) => {
412                self.monitor
413                    .notify(ConnectionKind::Listening(endpoint.clone()))
414                    .await;
415                ep
416            }
417            Err(err) => {
418                error!("Failed to listen on {endpoint}: {err}");
419                self.monitor
420                    .notify(ConnectionKind::ListenFailed(endpoint))
421                    .await;
422                return Err(err.into());
423            }
424        };
425
426        let resolved: Endpoint = quic_endpoint.local_endpoint().map_err(Error::from)?;
427        info!("Start listening on {resolved}");
428
429        self.task_group.spawn_then(
430            {
431                let this = self.clone();
432                async move { this.listen_loop_quic(quic_endpoint).await }
433            },
434            |res: TaskResult<()>| async move {
435                debug!("QUIC listen loop ended: {res}");
436            },
437        );
438
439        Ok(resolved)
440    }
441
442    /// QUIC accept loop.
443    #[cfg(feature = "quic")]
444    async fn listen_loop_quic(self: Arc<Self>, quic_endpoint: quic::QuicEndpoint) {
445        loop {
446            self.connection_slots.wait_for_slot().await;
447
448            // Only the accept runs here. The handshake and the wait for
449            // the peer's first stream both happen in the spawned task,
450            // so a slow peer cannot stall the loop.
451            let incoming = match quic_endpoint.accept_incoming().await {
452                Ok(c) => c,
453                Err(err) => {
454                    error!("Failed to accept QUIC conn: {err}");
455                    self.monitor.notify(ConnectionKind::AcceptFailed).await;
456                    continue;
457                }
458            };
459
460            let peer_ep = incoming.peer_endpoint();
461
462            if !self.access_control.allow(
463                &Subject::Endpoint(&peer_ep),
464                Action::Connect(ConnDirection::Inbound),
465            ) {
466                debug!("Rejected inbound QUIC connection from {peer_ep}");
467                continue;
468            }
469
470            self.monitor
471                .notify(ConnectionKind::Accepted(peer_ep.clone()))
472                .await;
473
474            self.connection_slots.add();
475
476            let on_disconnect = {
477                let this = self.clone();
478                |res: TaskResult<Result<()>>| async move {
479                    if let TaskResult::Completed(Err(err)) = res {
480                        debug!("Inbound QUIC conn dropped: {err}");
481                    }
482                    this.monitor
483                        .notify(ConnectionKind::Disconnected(peer_ep))
484                        .await;
485                    this.connection_slots.remove().await;
486                }
487            };
488
489            let conn_queue = self
490                .conn_queue
491                .as_ref()
492                .expect("QUIC listener requires ConnQueue")
493                .clone();
494            let handshake_timeout = self.handshake_timeout;
495            self.task_group.spawn_then(
496                async move {
497                    // Without this the only bound is the QUIC idle
498                    // timeout, which holds the slot far longer.
499                    let quic_conn = timeout(handshake_timeout, incoming.handshake()).await??;
500                    let vpid = quic_conn
501                        .peer_certificates()
502                        .as_deref()
503                        .and_then(peer_id_from_certs);
504                    // A peer that handshakes and then never opens a
505                    // stream only wastes its own slot.
506                    let stream = timeout(handshake_timeout, quic_conn.accept_stream()).await??;
507                    let conn: FramedConn<PeerNetMsgCodec> = framed(stream, PeerNetMsgCodec::new());
508                    conn_queue
509                        .handle_quic(conn, quic_conn, ConnDirection::Inbound, vpid)
510                        .await?;
511                    Ok(())
512                },
513                on_disconnect,
514            );
515        }
516    }
517}