Skip to main content

trillium_server_common/
quic.rs

1use crate::{Server, Transport};
2use futures_lite::{AsyncRead, AsyncWrite};
3use std::{
4    borrow::Cow,
5    fmt::Debug,
6    future::Future,
7    io,
8    net::SocketAddr,
9    pin::Pin,
10    sync::Arc,
11    task::{Context, Poll},
12};
13use trillium::Info;
14
15/// Abstraction over the inbound half of a QUIC stream (both bidi and inbound uni)
16pub trait QuicTransportReceive: AsyncRead {
17    /// Stop a receive stream, signaling an error code to the peer.
18    fn stop(&mut self, code: u64);
19}
20
21/// Abstraction over the outbound half of a QUIC stream (both bidi and outbound uni)
22pub trait QuicTransportSend: AsyncWrite {
23    /// Close the send stream immediately with the provided error code.
24    fn reset(&mut self, code: u64);
25
26    /// Set this stream's transmission priority relative to other streams on the same
27    /// connection. Higher values are sent first when the connection is send-constrained.
28    ///
29    /// The default does nothing, for transports without per-stream prioritization.
30    fn set_priority(&mut self, _priority: i32) {}
31}
32
33/// Abstraction over a QUIC bidirectional stream
34pub trait QuicTransportBidi: QuicTransportReceive + QuicTransportSend + Transport {}
35
36/// Abstraction over a single QUIC connection.
37///
38/// QUIC library adapters (e.g. trillium-quinn) implement this trait. The generic HTTP/3 connection
39/// handler in server-common consumes it to manage streams without knowing about the underlying QUIC
40/// implementation.
41///
42/// Implementations should be cheaply cloneable (typically wrapping an `Arc`-based connection
43/// handle) since the connection handler clones this into spawned tasks.
44pub trait QuicConnectionTrait: Clone + Send + Sync + 'static {
45    /// A bidirectional stream
46    type BidiStream: QuicTransportBidi + Unpin + Send + Sync + 'static;
47
48    /// A unidirectional receive stream from the peer
49    type RecvStream: QuicTransportReceive + Unpin + Send + Sync + 'static;
50
51    /// A unidirectional send stream to the peer
52    type SendStream: QuicTransportSend + Unpin + Send + Sync + 'static;
53
54    /// Accept the next bidirectional stream opened by the peer.
55    ///
56    /// Returns the QUIC stream ID and a combined read/write transport.
57    fn accept_bidi(&self) -> impl Future<Output = io::Result<(u64, Self::BidiStream)>> + Send;
58
59    /// Accept the next unidirectional stream opened by the peer.
60    ///
61    /// Returns the QUIC stream ID and a receive-only stream.
62    fn accept_uni(&self) -> impl Future<Output = io::Result<(u64, Self::RecvStream)>> + Send;
63
64    /// Open a new unidirectional stream to the peer.
65    ///
66    /// Returns the QUIC stream ID and a send-only stream.
67    fn open_uni(&self) -> impl Future<Output = io::Result<(u64, Self::SendStream)>> + Send;
68
69    /// Open a new bidirectional stream to the peer.
70    ///
71    /// Returns the QUIC stream ID and a combined read/write transport.
72    fn open_bidi(&self) -> impl Future<Output = io::Result<(u64, Self::BidiStream)>> + Send;
73
74    /// The peer's address.
75    fn remote_address(&self) -> SocketAddr;
76
77    /// Close the entire QUIC connection with an error code and reason.
78    fn close(&self, error_code: u64, reason: &[u8]);
79
80    /// Send an unreliable datagram over the QUIC connection.
81    ///
82    /// Datagrams are atomic and unordered. The data must fit in a single QUIC packet
83    /// (typically ~1200 bytes). Returns an error if datagrams are not supported by the
84    /// peer or the data is too large.
85    fn send_datagram(&self, data: &[u8]) -> io::Result<()>;
86
87    /// Receive the next unreliable datagram from the peer, passing the raw bytes to `callback`.
88    fn recv_datagram<F: FnOnce(&[u8]) + Send>(
89        &self,
90        callback: F,
91    ) -> impl Future<Output = io::Result<()>> + Send;
92
93    /// The maximum datagram payload size the peer will accept, if datagrams are supported.
94    ///
95    /// Returns `None` if the peer does not support datagrams.
96    fn max_datagram_size(&self) -> Option<usize>;
97}
98
99/// Configuration for a QUIC endpoint, provided by the user at server setup time.
100///
101/// QUIC library adapters implement this (e.g. `trillium_quinn::QuicConfig`). The `()`
102/// implementation produces no binding (HTTP/3 disabled).
103///
104/// The generic flow is:
105/// 1. User provides a `QuicConfig` via [`Config::with_quic`](crate::Config)
106/// 2. During server startup, `bind` is called with the TCP listener's address and runtime
107/// 3. The resulting [`QuicEndpoint`] is stored on `RunningConfig` and drives the H3 accept loop
108pub trait QuicConfig<S: Server>: Send + 'static {
109    /// The bound endpoint type produced by [`bind`](QuicConfig::bind).
110    type Endpoint: QuicEndpoint;
111
112    /// Bind a QUIC endpoint to the given address.
113    ///
114    /// The runtime is provided so that QUIC library adapters can bridge
115    /// to the active async runtime for timers, spawning, and UDP I/O.
116    ///
117    /// Returns `None` if QUIC is not configured (the `()` case), `Some(Ok(binding))` on success,
118    /// or `Some(Err(..))` if binding fails.
119    fn bind(
120        self,
121        addr: SocketAddr,
122        runtime: S::Runtime,
123        info: &mut Info,
124    ) -> Option<io::Result<Self::Endpoint>>;
125
126    /// Whether this is a real QUIC configuration (`true`) rather than the no-op `()` (`false`).
127    ///
128    /// Lets a caller decide whether to set up a QUIC listener at all without consuming `self` by
129    /// calling [`bind`](Self::bind). The default returns `true`; only the `()` implementation
130    /// overrides it.
131    fn is_configured(&self) -> bool {
132        true
133    }
134
135    /// Bind a QUIC endpoint over a pre-claimed [`std::net::UdpSocket`].
136    ///
137    /// The multi-listener server builder claims the UDP socket eagerly (fail-fast at
138    /// `bind_quic` time) and hands it through to the adapter when the runtime is available.
139    /// The default implementation reads the socket's local address and delegates to
140    /// [`bind`](Self::bind), which is correct but rebinds the address; adapters should override
141    /// to consume the pre-claimed socket directly (e.g. quinn accepts a `std::net::UdpSocket`
142    /// in `Endpoint::new`).
143    fn bind_with_socket(
144        self,
145        socket: std::net::UdpSocket,
146        runtime: S::Runtime,
147        info: &mut Info,
148    ) -> io::Result<Self::Endpoint>
149    where
150        Self: Sized,
151    {
152        let addr = socket.local_addr()?;
153        drop(socket);
154        self.bind(addr, runtime, info).unwrap_or_else(|| {
155            Err(io::Error::new(
156                io::ErrorKind::Unsupported,
157                "QuicConfig::bind returned None; this QuicConfig is a no-op and cannot be bound \
158                 with bind_with_socket",
159            ))
160        })
161    }
162}
163
164impl<S: Server> QuicConfig<S> for () {
165    type Endpoint = ();
166
167    fn bind(self, _: SocketAddr, _: S::Runtime, _: &mut Info) -> Option<io::Result<()>> {
168        None
169    }
170
171    fn is_configured(&self) -> bool {
172        false
173    }
174}
175
176/// A bound QUIC endpoint that accepts and initiates connections.
177///
178/// Analogous to [`Server`](crate::Server) for TCP. QUIC library adapters implement this to provide
179/// the connection accept loop (server) and outbound connections (client).
180///
181/// The `()` implementation is a no-op (HTTP/3 disabled). Server-only implementations may return
182/// an error from [`connect`](QuicEndpoint::connect); client-only implementations may return
183/// `None` from [`accept`](QuicEndpoint::accept).
184pub trait QuicEndpoint: Send + Sync + 'static {
185    /// The connection type yielded by this endpoint.
186    type Connection: QuicConnectionTrait;
187
188    /// Accept the next inbound QUIC connection, or return `None` if the endpoint is done.
189    fn accept(&self) -> impl Future<Output = Option<Self::Connection>> + Send;
190
191    /// Initiate a QUIC connection to the given address.
192    ///
193    /// `server_name` is the SNI hostname used for TLS verification.
194    fn connect(
195        &self,
196        addr: SocketAddr,
197        server_name: &str,
198    ) -> impl Future<Output = io::Result<Self::Connection>> + Send;
199
200    /// Initiate a QUIC connection advertising `alpn` for this connection only, overriding the
201    /// endpoint's configured default ALPN. An empty list uses the default.
202    ///
203    /// Lets one bound endpoint negotiate different application protocols per connection (e.g. `h3`
204    /// for HTTP/3 origins and `doq` for a DNS-over-QUIC resolver) over the same UDP socket. The
205    /// default implementation ignores `alpn` and calls [`connect`](QuicEndpoint::connect); adapters
206    /// that can vary ALPN per connection override it.
207    fn connect_with_alpn(
208        &self,
209        addr: SocketAddr,
210        server_name: &str,
211        alpn: &[Cow<'static, [u8]>],
212    ) -> impl Future<Output = io::Result<Self::Connection>> + Send {
213        let _ = alpn;
214        self.connect(addr, server_name)
215    }
216
217    /// The local address this endpoint is bound to. The default impl returns
218    /// `Unsupported`; adapters override when a bound UDP socket is available.
219    fn local_addr(&self) -> io::Result<SocketAddr> {
220        Err(io::Error::new(
221            io::ErrorKind::Unsupported,
222            "QuicEndpoint::local_addr not implemented for this adapter",
223        ))
224    }
225}
226
227/// Uninhabited type used by the `()` [`QuicEndpoint`] implementation.
228///
229/// Since `()` never produces connections, this type is never constructed and its trait
230/// implementations are never exercised.
231#[derive(Debug, Clone, Copy)]
232pub enum NoQuic {}
233
234impl QuicTransportSend for NoQuic {
235    fn reset(&mut self, _code: u64) {
236        match *self {}
237    }
238}
239
240impl QuicTransportReceive for NoQuic {
241    fn stop(&mut self, _code: u64) {
242        match *self {}
243    }
244}
245
246impl QuicTransportBidi for NoQuic {}
247
248impl QuicConnectionTrait for NoQuic {
249    type BidiStream = NoQuic;
250    type RecvStream = NoQuic;
251    type SendStream = NoQuic;
252
253    async fn accept_bidi(&self) -> io::Result<(u64, Self::BidiStream)> {
254        match *self {}
255    }
256
257    async fn accept_uni(&self) -> io::Result<(u64, Self::RecvStream)> {
258        match *self {}
259    }
260
261    async fn open_uni(&self) -> io::Result<(u64, Self::SendStream)> {
262        match *self {}
263    }
264
265    async fn open_bidi(&self) -> io::Result<(u64, Self::BidiStream)> {
266        match *self {}
267    }
268
269    fn remote_address(&self) -> SocketAddr {
270        match *self {}
271    }
272
273    fn close(&self, _: u64, _: &[u8]) {
274        match *self {}
275    }
276
277    fn send_datagram(&self, _: &[u8]) -> io::Result<()> {
278        match *self {}
279    }
280
281    async fn recv_datagram<F: FnOnce(&[u8]) + Send>(&self, _: F) -> io::Result<()> {
282        match *self {}
283    }
284
285    fn max_datagram_size(&self) -> Option<usize> {
286        match *self {}
287    }
288}
289
290impl Transport for NoQuic {}
291
292impl AsyncRead for NoQuic {
293    fn poll_read(
294        self: Pin<&mut Self>,
295        _: &mut Context<'_>,
296        _: &mut [u8],
297    ) -> Poll<io::Result<usize>> {
298        match *self.get_mut() {}
299    }
300}
301
302impl AsyncWrite for NoQuic {
303    fn poll_write(self: Pin<&mut Self>, _: &mut Context<'_>, _: &[u8]) -> Poll<io::Result<usize>> {
304        match *self.get_mut() {}
305    }
306
307    fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
308        match *self.get_mut() {}
309    }
310
311    fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
312        match *self.get_mut() {}
313    }
314}
315
316impl QuicEndpoint for () {
317    type Connection = NoQuic;
318
319    async fn accept(&self) -> Option<NoQuic> {
320        None
321    }
322
323    async fn connect(&self, _: SocketAddr, _: &str) -> io::Result<NoQuic> {
324        Err(io::Error::new(
325            io::ErrorKind::Unsupported,
326            "QUIC not configured",
327        ))
328    }
329}
330
331// -- Type-erased QuicConnection --
332
333type BoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
334
335/// A type-erased [`QuicTransportReceive`] stream — `Box<dyn QuicTransportReceive + Unpin + Send +
336/// Sync>`.
337pub type BoxedRecvStream = Box<dyn QuicTransportReceive + Unpin + Send + Sync>;
338
339/// A type-erased [`QuicTransportSend`] stream — `Box<dyn QuicTransportSend + Unpin + Send + Sync>`.
340pub type BoxedSendStream = Box<dyn QuicTransportSend + Unpin + Send + Sync>;
341
342/// A type-erased [`QuicTransportBidi`] stream — `Box<dyn QuicTransportBidi + Unpin + Send + Sync>`.
343pub type BoxedBidiStream = Box<dyn QuicTransportBidi + Unpin + Send + Sync>;
344
345impl QuicTransportReceive for BoxedRecvStream {
346    fn stop(&mut self, code: u64) {
347        (**self).stop(code);
348    }
349}
350
351impl QuicTransportSend for BoxedSendStream {
352    fn reset(&mut self, code: u64) {
353        (**self).reset(code);
354    }
355
356    fn set_priority(&mut self, priority: i32) {
357        (**self).set_priority(priority);
358    }
359}
360
361impl QuicTransportReceive for BoxedBidiStream {
362    fn stop(&mut self, code: u64) {
363        (**self).stop(code);
364    }
365}
366
367impl QuicTransportSend for BoxedBidiStream {
368    fn reset(&mut self, code: u64) {
369        (**self).reset(code);
370    }
371
372    fn set_priority(&mut self, priority: i32) {
373        (**self).set_priority(priority);
374    }
375}
376
377impl QuicTransportBidi for BoxedBidiStream {}
378
379impl Transport for BoxedBidiStream {
380    fn set_linger(&mut self, linger: Option<std::time::Duration>) -> io::Result<()> {
381        (**self).set_linger(linger)
382    }
383
384    fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
385        (**self).set_nodelay(nodelay)
386    }
387
388    fn set_ip_ttl(&mut self, ttl: u32) -> io::Result<()> {
389        (**self).set_ip_ttl(ttl)
390    }
391
392    fn peer_addr(&self) -> io::Result<Option<SocketAddr>> {
393        (**self).peer_addr()
394    }
395
396    fn negotiated_alpn(&self) -> Option<Cow<'_, [u8]>> {
397        (**self).negotiated_alpn()
398    }
399}
400
401type ReceiveDatagramCallback<'a> = Box<dyn FnOnce(&[u8]) + Send + 'a>;
402
403trait ObjectSafeQuicConnection: Send + Sync {
404    fn accept_bidi(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedBidiStream)>>;
405    fn accept_uni(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedRecvStream)>>;
406    fn open_uni(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedSendStream)>>;
407    fn open_bidi(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedBidiStream)>>;
408    fn remote_address(&self) -> SocketAddr;
409    fn close(&self, error_code: u64, reason: &[u8]);
410    fn send_datagram(&self, data: &[u8]) -> io::Result<()>;
411    fn recv_datagram<'a>(
412        &'a self,
413        callback: ReceiveDatagramCallback<'a>,
414    ) -> BoxedFuture<'a, io::Result<()>>;
415    fn max_datagram_size(&self) -> Option<usize>;
416}
417
418impl<T: QuicConnectionTrait> ObjectSafeQuicConnection for T {
419    fn accept_bidi(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedBidiStream)>> {
420        Box::pin(async {
421            let (id, stream) = QuicConnectionTrait::accept_bidi(self).await?;
422            Ok((id, Box::new(stream) as BoxedBidiStream))
423        })
424    }
425
426    fn accept_uni(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedRecvStream)>> {
427        Box::pin(async {
428            let (id, stream) = QuicConnectionTrait::accept_uni(self).await?;
429            Ok((id, Box::new(stream) as BoxedRecvStream))
430        })
431    }
432
433    fn open_uni(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedSendStream)>> {
434        Box::pin(async {
435            let (id, stream) = QuicConnectionTrait::open_uni(self).await?;
436            Ok((id, Box::new(stream) as BoxedSendStream))
437        })
438    }
439
440    fn open_bidi(&self) -> BoxedFuture<'_, io::Result<(u64, BoxedBidiStream)>> {
441        Box::pin(async {
442            let (id, stream) = QuicConnectionTrait::open_bidi(self).await?;
443            Ok((id, Box::new(stream) as BoxedBidiStream))
444        })
445    }
446
447    fn remote_address(&self) -> SocketAddr {
448        QuicConnectionTrait::remote_address(self)
449    }
450
451    fn close(&self, error_code: u64, reason: &[u8]) {
452        QuicConnectionTrait::close(self, error_code, reason)
453    }
454
455    fn send_datagram(&self, data: &[u8]) -> io::Result<()> {
456        QuicConnectionTrait::send_datagram(self, data)
457    }
458
459    fn recv_datagram<'a>(
460        &'a self,
461        callback: Box<dyn FnOnce(&[u8]) + Send + 'a>,
462    ) -> BoxedFuture<'a, io::Result<()>> {
463        Box::pin(QuicConnectionTrait::recv_datagram(self, callback))
464    }
465
466    fn max_datagram_size(&self) -> Option<usize> {
467        QuicConnectionTrait::max_datagram_size(self)
468    }
469}
470
471/// A type-erased QUIC connection handle, equivalent to `Arc<dyn QuicConnectionTrait>`.
472/// Cheaply cloneable.
473///
474/// Handlers retrieve this from conn state to access QUIC features (streams, datagrams)
475/// without depending on the concrete QUIC implementation type.
476#[derive(Clone)]
477pub struct QuicConnection(Arc<dyn ObjectSafeQuicConnection>);
478
479impl Debug for QuicConnection {
480    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
481        f.debug_struct("QuicConnection")
482            .field("peer", &self.remote_address())
483            .finish_non_exhaustive()
484    }
485}
486
487impl<T: QuicConnectionTrait> From<T> for QuicConnection {
488    fn from(connection: T) -> Self {
489        Self(Arc::new(connection))
490    }
491}
492
493impl QuicConnection {
494    /// Accept the next bidirectional stream opened by the peer.
495    pub async fn accept_bidi(&self) -> io::Result<(u64, BoxedBidiStream)> {
496        self.0.accept_bidi().await
497    }
498
499    /// Accept the next unidirectional stream opened by the peer.
500    pub async fn accept_uni(&self) -> io::Result<(u64, BoxedRecvStream)> {
501        self.0.accept_uni().await
502    }
503
504    /// Open a new unidirectional stream to the peer.
505    pub async fn open_uni(&self) -> io::Result<(u64, BoxedSendStream)> {
506        self.0.open_uni().await
507    }
508
509    /// Open a new bidirectional stream to the peer.
510    pub async fn open_bidi(&self) -> io::Result<(u64, BoxedBidiStream)> {
511        self.0.open_bidi().await
512    }
513
514    /// The peer's address.
515    pub fn remote_address(&self) -> SocketAddr {
516        self.0.remote_address()
517    }
518
519    /// Close the entire QUIC connection with an error code and reason.
520    pub fn close(&self, error_code: u64, reason: &[u8]) {
521        self.0.close(error_code, reason)
522    }
523
524    /// Send an unreliable datagram over the QUIC connection.
525    pub fn send_datagram(&self, data: &[u8]) -> io::Result<()> {
526        self.0.send_datagram(data)
527    }
528
529    /// Receive the next unreliable datagram from the peer, passing the raw bytes to `callback`.
530    pub async fn recv_datagram<'a, F: FnOnce(&[u8]) + Send + 'a>(
531        &'a self,
532        callback: F,
533    ) -> io::Result<()> {
534        self.0.recv_datagram(Box::new(callback)).await
535    }
536
537    /// The maximum datagram payload size the peer will accept, if datagrams are supported.
538    pub fn max_datagram_size(&self) -> Option<usize> {
539        self.0.max_datagram_size()
540    }
541}
542
543// -- Type-erased QuicEndpoint --
544
545trait ObjectSafeQuicEndpoint: Send + Sync {
546    fn accept(&self) -> BoxedFuture<'_, Option<QuicConnection>>;
547    fn connect<'a>(
548        &'a self,
549        addr: SocketAddr,
550        server_name: &'a str,
551    ) -> BoxedFuture<'a, io::Result<QuicConnection>>;
552    fn connect_with_alpn<'a>(
553        &'a self,
554        addr: SocketAddr,
555        server_name: &'a str,
556        alpn: &'a [Cow<'static, [u8]>],
557    ) -> BoxedFuture<'a, io::Result<QuicConnection>>;
558    fn local_addr(&self) -> io::Result<SocketAddr>;
559}
560
561impl<T: QuicEndpoint> ObjectSafeQuicEndpoint for T {
562    fn accept(&self) -> BoxedFuture<'_, Option<QuicConnection>> {
563        Box::pin(async { QuicEndpoint::accept(self).await.map(QuicConnection::from) })
564    }
565
566    fn connect<'a>(
567        &'a self,
568        addr: SocketAddr,
569        server_name: &'a str,
570    ) -> BoxedFuture<'a, io::Result<QuicConnection>> {
571        Box::pin(async move {
572            QuicEndpoint::connect(self, addr, server_name)
573                .await
574                .map(QuicConnection::from)
575        })
576    }
577
578    fn connect_with_alpn<'a>(
579        &'a self,
580        addr: SocketAddr,
581        server_name: &'a str,
582        alpn: &'a [Cow<'static, [u8]>],
583    ) -> BoxedFuture<'a, io::Result<QuicConnection>> {
584        Box::pin(async move {
585            QuicEndpoint::connect_with_alpn(self, addr, server_name, alpn)
586                .await
587                .map(QuicConnection::from)
588        })
589    }
590
591    fn local_addr(&self) -> io::Result<SocketAddr> {
592        QuicEndpoint::local_addr(self)
593    }
594}
595
596/// A type-erased QUIC endpoint, equivalent to `Arc<dyn QuicEndpoint>`.
597/// Cheaply cloneable.
598#[derive(Clone)]
599pub struct ArcedQuicEndpoint(Arc<dyn ObjectSafeQuicEndpoint>);
600
601impl Debug for ArcedQuicEndpoint {
602    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
603        f.debug_tuple("ArcedQuicEndpoint").finish()
604    }
605}
606
607impl<T: QuicEndpoint> From<T> for ArcedQuicEndpoint {
608    fn from(endpoint: T) -> Self {
609        Self(Arc::new(endpoint))
610    }
611}
612
613impl ArcedQuicEndpoint {
614    /// Accept the next inbound QUIC connection.
615    pub async fn accept(&self) -> Option<QuicConnection> {
616        self.0.accept().await
617    }
618
619    /// Initiate a QUIC connection to the given address.
620    pub async fn connect(&self, addr: SocketAddr, server_name: &str) -> io::Result<QuicConnection> {
621        self.0.connect(addr, server_name).await
622    }
623
624    /// Initiate a QUIC connection advertising `alpn` for this connection only, overriding the
625    /// endpoint's configured default ALPN. An empty list uses the default.
626    pub async fn connect_with_alpn(
627        &self,
628        addr: SocketAddr,
629        server_name: &str,
630        alpn: &[Cow<'static, [u8]>],
631    ) -> io::Result<QuicConnection> {
632        self.0.connect_with_alpn(addr, server_name, alpn).await
633    }
634
635    /// The local address this endpoint is bound to, if the adapter supports reporting it.
636    pub fn local_addr(&self) -> io::Result<SocketAddr> {
637        self.0.local_addr()
638    }
639}