Skip to main content

ax_net/
udp.rs

1//! UDP socket implementation.
2//!
3//! UDP sockets wrap smoltcp datagram sockets with POSIX-style bind/connect,
4//! per-address port ownership, connected-peer filtering, route-aware source
5//! selection, and MSG_MORE corking for datagram coalescing.
6//!
7//! # Bind And Routing Semantics
8//!
9//! Public binds are checked through `SocketSetWrapper` so wildcard and specific
10//! address conflicts match Linux expectations. When a socket is connected or
11//! sends to a destination, the control plane selects the source address and
12//! device binding from the route table unless the socket was explicitly bound
13//! to a concrete local address/interface.
14//!
15//! # Datagram Semantics
16//!
17//! smoltcp stores UDP payload plus metadata, while the POSIX surface exposes
18//! per-call source addresses, `MSG_TRUNC`, `MSG_PEEK`, `MSG_DONTWAIT`, and
19//! `MSG_MORE`. This module is responsible for preserving message boundaries and
20//! for filtering connected sockets to their expected peer.
21//!
22//! # Polling
23//!
24//! UDP send/recv operations request the unique protocol executor after socket
25//! state changes. They do not run the interface poll loop directly.
26
27use alloc::{boxed::Box, sync::Arc, vec, vec::Vec};
28use core::{
29    net::{IpAddr, Ipv4Addr, SocketAddr},
30    task::Waker,
31};
32
33use ax_io::prelude::*;
34use ax_sync::{Mutex, SpinLock};
35use axpoll::{ExclusiveRegistrationSink, IoEvents, Pollable, SharedRegistrationSink};
36use axpoll_set::PollSet;
37use smoltcp::{
38    iface::SocketHandle,
39    phy::PacketMeta,
40    socket::udp::{self as smol, UdpMetadata},
41    storage::PacketMetadata,
42    wire::{IpAddress, IpEndpoint, IpListenEndpoint, IpProtocol},
43};
44
45use crate::{
46    ConnectStatus, IpCmsg, NetError, NetResult, ReadinessVersion, RecvFlags, RecvOptions,
47    SOCKET_SET, SendFlags, SendOptions, Shutdown, SocketAddrEx, SocketDeferPollWake, SocketOps,
48    addr::allocate_ephemeral_port,
49    config::{DeviceBinding, InterfaceId},
50    consts::{UDP_RX_BUF_LEN, UDP_TX_BUF_LEN},
51    flush_egress,
52    general::GeneralOptions,
53    get_control, interface_by_id,
54    ip_tos::{EgressIpTosKey, clear_egress_ip_tos, set_egress_ip_tos},
55    options::{Configurable, GetSocketOption, SetSocketOption},
56    request_poll,
57    rx_meta::{ReceivedTrafficClass, received_traffic_class},
58};
59
60/// Buffered state for MSG_MORE corking: captures the target endpoint
61/// and source address at the first MSG_MORE call so the merged datagram
62/// is always delivered to the correct peer regardless of subsequent
63/// calls' addresses.
64struct CorkState {
65    buf: Vec<u8>,
66    remote: IpEndpoint,
67    source: IpAddress,
68}
69
70/// A UDP socket that provides POSIX-like APIs.
71pub struct UdpSocket {
72    /// Handle into the global smoltcp socket set.
73    handle: SocketHandle,
74    /// Serializes the multi-step public bind transaction.
75    bind_lock: Mutex<()>,
76    /// Bound local endpoint as exposed by POSIX socket calls.
77    local_addr: Mutex<Option<IpEndpoint>>,
78    /// Connected remote endpoint plus selected source address.
79    peer_addr: Mutex<Option<(IpEndpoint, IpAddress)>>,
80
81    /// Shared socket options and blocking helpers.
82    general: GeneralOptions,
83    /// Multiplexes protocol and timer wakeups to owned poll registrations.
84    poll_state: Arc<PollSet>,
85    /// Generation published for each socket readiness wake.
86    readiness_version: ReadinessVersion,
87    /// Egress IP_TOS policies registered for recently used UDP destinations.
88    tos_keys: SpinLock<Vec<EgressIpTosKey>>,
89    /// MSG_MORE corking state: captures endpoint at first MSG_MORE
90    /// so the merged datagram always goes to the correct peer.
91    // Linux serializes UDP corking with the process-context socket lock. This
92    // state may remain held while the global protocol socket is contended, so
93    // it must be sleepable rather than an IRQ-disabling raw spin lock.
94    cork: Mutex<Option<CorkState>>,
95}
96
97impl UdpSocket {
98    /// Creates a new UDP socket.
99    pub fn new() -> Self {
100        Self {
101            handle: SOCKET_SET.add(smol::Socket::new(
102                smol::PacketBuffer::new(vec![PacketMetadata::EMPTY; 256], vec![0; UDP_RX_BUF_LEN]),
103                smol::PacketBuffer::new(vec![PacketMetadata::EMPTY; 256], vec![0; UDP_TX_BUF_LEN]),
104            )),
105            bind_lock: Mutex::new(()),
106            local_addr: Mutex::new(None),
107            peer_addr: Mutex::new(None),
108
109            general: GeneralOptions::new(2, 2, 17), // SOCK_DGRAM
110            poll_state: Arc::new(PollSet::new()),
111            readiness_version: ReadinessVersion::new(),
112            tos_keys: SpinLock::new(Vec::new()),
113            cork: Mutex::new(None),
114        }
115    }
116
117    /// Returns the latest readiness wake generation for edge-triggered pollers.
118    pub fn readiness_version(&self) -> u64 {
119        self.readiness_version.current()
120    }
121
122    /// Restricts this socket to one interface for route selection.
123    pub fn bind_device(&self, interface_id: InterfaceId) -> NetResult {
124        if interface_by_id(interface_id).is_none() {
125            return Err(NetError::NoSuchDevice);
126        }
127        self.general.set_device_binding(DeviceBinding {
128            bound_if: Some(interface_id),
129        });
130        Ok(())
131    }
132
133    /// Borrows the underlying smoltcp UDP socket by handle.
134    fn with_smol_socket<R>(&self, f: impl FnOnce(&mut smol::Socket) -> R) -> R {
135        SOCKET_SET.with_socket_mut::<smol::Socket, _, _>(self.handle, f)
136    }
137
138    /// Returns the connected peer and cached source address.
139    fn remote_endpoint(&self) -> NetResult<(IpEndpoint, IpAddress)> {
140        match self.peer_addr.try_lock() {
141            Some(addr) => addr.ok_or(NetError::NotConnected),
142            None => Err(NetError::NotConnected),
143        }
144    }
145
146    /// Selects the source address used to reach `remote`.
147    fn source_for_remote(&self, remote: &IpAddress) -> NetResult<IpAddress> {
148        Ok(get_control()
149            .select_route_with_binding(remote, self.general.device_binding())?
150            .source)
151    }
152
153    fn send_source_for_remote(&self, remote: &IpAddress) -> NetResult<IpAddress> {
154        if let Some(local_ep) = *self.local_addr.lock()
155            && !local_ep.addr.is_unspecified()
156        {
157            Ok(local_ep.addr)
158        } else {
159            self.source_for_remote(remote)
160        }
161    }
162
163    fn source_and_binding_update_for_remote(
164        &self,
165        remote: &IpAddress,
166    ) -> NetResult<(IpAddress, bool)> {
167        if let Some(local_ep) = *self.local_addr.lock()
168            && !local_ep.addr.is_unspecified()
169        {
170            Ok((local_ep.addr, false))
171        } else {
172            Ok((self.source_for_remote(remote)?, true))
173        }
174    }
175
176    fn track_egress_ip_tos(&self, local_addr: Option<IpAddress>, remote: IpEndpoint) {
177        let tos = self.general.ip_tos();
178        if tos == 0 {
179            return;
180        }
181        let Some(local_addr) = local_addr else {
182            return;
183        };
184        let Some(local) = self.local_addr.lock().map(|endpoint| IpEndpoint {
185            addr: local_addr,
186            port: endpoint.port,
187        }) else {
188            return;
189        };
190        let Some(key) = EgressIpTosKey::exact(IpProtocol::Udp, local, remote) else {
191            return;
192        };
193
194        let mut keys = self.tos_keys.lock();
195        if !keys.contains(&key) {
196            keys.push(key);
197        }
198        set_egress_ip_tos(key, tos);
199    }
200
201    fn sync_tracked_egress_ip_tos(&self) {
202        let tos = self.general.ip_tos();
203        for key in self.tos_keys.lock().iter().copied() {
204            set_egress_ip_tos(key, tos);
205        }
206    }
207
208    fn clear_tracked_egress_ip_tos(&self) {
209        for key in self.tos_keys.lock().drain(..) {
210            clear_egress_ip_tos(key);
211        }
212    }
213}
214
215impl Configurable for UdpSocket {
216    fn get_option_inner(&self, option: &mut GetSocketOption) -> NetResult<bool> {
217        use GetSocketOption as O;
218
219        if self.general.get_option_inner(option)? {
220            return Ok(true);
221        }
222        match option {
223            O::Ttl(ttl) => {
224                self.with_smol_socket(|socket| {
225                    **ttl = socket.hop_limit().unwrap_or(64);
226                });
227            }
228            O::SendBuffer(size) => {
229                **size = UDP_TX_BUF_LEN;
230            }
231            O::ReceiveBuffer(size) => {
232                **size = UDP_RX_BUF_LEN;
233            }
234            _ => return Ok(false),
235        }
236        Ok(true)
237    }
238
239    fn set_option_inner(&self, option: SetSocketOption) -> NetResult<bool> {
240        use SetSocketOption as O;
241
242        if let O::IpTos(tos) = option {
243            self.general.set_ip_tos(*tos);
244            self.sync_tracked_egress_ip_tos();
245            return Ok(true);
246        }
247
248        if self.general.set_option_inner(option)? {
249            return Ok(true);
250        }
251        match option {
252            O::Ttl(ttl) => {
253                self.with_smol_socket(|socket| {
254                    socket.set_hop_limit(Some(*ttl));
255                });
256            }
257            _ => return Ok(false),
258        }
259        Ok(true)
260    }
261}
262impl SocketOps for UdpSocket {
263    /// Binds the UDP socket and records public port ownership.
264    fn bind(&self, local_addr: SocketAddrEx) -> NetResult {
265        let mut local_addr = local_addr.into_ip()?;
266        let _bind_guard = self.bind_lock.lock();
267
268        if self.local_addr.lock().is_some() {
269            return Err(NetError::InvalidInput);
270        }
271        if local_addr.port() == 0 {
272            local_addr.set_port(get_ephemeral_port()?);
273        }
274
275        let local_endpoint = IpEndpoint::from(local_addr);
276        let endpoint = IpListenEndpoint {
277            addr: (!local_endpoint.addr.is_unspecified()).then_some(local_endpoint.addr),
278            port: local_endpoint.port,
279        };
280        let binding = get_control().local_binding_for(&endpoint)?;
281
282        self.with_smol_socket(|socket| {
283            socket.bind(endpoint).map_err(|e| match e {
284                smol::BindError::InvalidState => NetError::InvalidInput,
285                smol::BindError::Unaddressable => NetError::ConnectionRefused,
286            })
287        })?;
288        if let Err(err) = SOCKET_SET.udp_bind(
289            self.handle,
290            local_endpoint.addr,
291            local_endpoint.port,
292            self.general.reuse_port(),
293        ) {
294            self.with_smol_socket(|socket| socket.close());
295            return Err(err);
296        }
297        if binding.bound_if.is_some() {
298            self.general.set_device_binding(binding);
299        }
300
301        *self.local_addr.lock() = Some(local_endpoint);
302        info!("UDP socket {}: bound on {}", self.handle, endpoint);
303        Ok(())
304    }
305
306    /// Stores a default peer and source address for connected UDP semantics.
307    fn start_connect(&self, remote_addr: SocketAddrEx) -> NetResult<ConnectStatus> {
308        let remote_addr = remote_addr.into_ip()?;
309        let mut guard = self.peer_addr.lock();
310
311        if self.local_addr.lock().is_none() {
312            self.bind(SocketAddrEx::Ip(SocketAddr::new(
313                IpAddr::V4(Ipv4Addr::UNSPECIFIED),
314                0,
315            )))?;
316        }
317
318        let remote_addr = IpEndpoint::from(remote_addr);
319        let local_port = self.local_addr.lock().map_or(0, |endpoint| endpoint.port);
320        let (src, should_update_binding) =
321            self.source_and_binding_update_for_remote(&remote_addr.addr)?;
322
323        *guard = Some((remote_addr, src));
324
325        if should_update_binding {
326            self.general
327                .set_device_binding(get_control().local_binding_for(&IpListenEndpoint {
328                    addr: Some(src),
329                    port: local_port,
330                })?);
331        }
332
333        debug!("UDP socket {}: connected to {}", self.handle, remote_addr);
334        Ok(ConnectStatus::Connected)
335    }
336
337    /// Sends one datagram, or appends to/flushed a MSG_MORE corked datagram.
338    fn try_send(&self, mut src: impl Read + IoBuf, options: &mut SendOptions) -> NetResult<usize> {
339        // MSG_OOB is only valid on stream sockets (SOCK_STREAM), not DGRAM.
340        if options.flags.contains(SendFlags::OOB) {
341            return Err(NetError::OperationNotSupported);
342        }
343
344        if self.local_addr.lock().is_none() {
345            self.bind(SocketAddrEx::Ip(SocketAddr::new(
346                IpAddr::V4(Ipv4Addr::UNSPECIFIED),
347                0,
348            )))?;
349        }
350        // MSG_MORE corking: buffer data instead of sending immediately.
351        // Cap corked data to the UDP TX buffer size to prevent
352        // unbounded kernel memory allocation from user input.
353        const CORK_MAX: usize = UDP_TX_BUF_LEN;
354        let more = options.flags.contains(SendFlags::MORE);
355
356        if more {
357            let (remote_addr, source_addr) = match options.to.clone() {
358                Some(addr) => {
359                    let addr = IpEndpoint::from(addr.into_ip()?);
360                    let src = self.send_source_for_remote(&addr.addr)?;
361                    (addr, src)
362                }
363                None => match self.remote_endpoint() {
364                    Ok((endpoint, src)) => (endpoint, src),
365                    Err(_) => return Err(NetError::DestAddrRequired),
366                },
367            };
368            if remote_addr.port == 0 || remote_addr.addr.is_unspecified() {
369                return Err(NetError::InvalidInput);
370            }
371            let len = src.remaining();
372            if len > CORK_MAX {
373                return Err(NetError::MessageTooLong);
374            }
375            let mut tmp = alloc::vec![0u8; len];
376            let read = src.read(&mut tmp)?;
377            let mut cork = self.cork.lock();
378            if cork.is_none() {
379                *cork = Some(CorkState {
380                    buf: tmp[..read].to_vec(),
381                    remote: remote_addr,
382                    source: source_addr,
383                });
384            } else {
385                let prev = cork.as_ref().unwrap().buf.len();
386                let new_len = prev.checked_add(read).ok_or(NetError::MessageTooLong)?;
387                if new_len > CORK_MAX {
388                    return Err(NetError::MessageTooLong);
389                }
390                cork.as_mut().unwrap().buf.extend_from_slice(&tmp[..read]);
391            }
392            return Ok(read);
393        }
394
395        // Resolve destination for direct send or cork flush.
396        // None means unconnected socket without explicit destination;
397        // the poller closure checks cork before demanding an address.
398        let resolved = match options.to.clone() {
399            Some(addr) => {
400                let addr = IpEndpoint::from(addr.into_ip()?);
401                let src = self.send_source_for_remote(&addr.addr)?;
402                Some((addr, src))
403            }
404            None => self.remote_endpoint().ok(),
405        };
406
407        request_poll();
408        let mut cork_guard = self.cork.lock();
409        // When flushing corked data, always use the endpoint captured
410        // at the first MSG_MORE call (matching Linux semantics).
411        let (endpoint, local_addr, payload_len) = if let Some(ref c) = *cork_guard {
412            let total = c
413                .buf
414                .len()
415                .checked_add(src.remaining())
416                .ok_or(NetError::MessageTooLong)?;
417            if total > CORK_MAX {
418                return Err(NetError::MessageTooLong);
419            }
420            (c.remote, Some(c.source), total)
421        } else {
422            match resolved {
423                Some((remote, source)) => {
424                    if remote.port == 0 || remote.addr.is_unspecified() {
425                        return Err(NetError::InvalidInput);
426                    }
427                    (remote, Some(source), src.remaining())
428                }
429                None => return Err(NetError::DestAddrRequired),
430            }
431        };
432        let result = self.with_smol_socket(|socket| {
433            if !socket.is_open() {
434                // not connected
435                Err(NetError::NotConnected)
436            } else if !socket.can_send() {
437                Err(NetError::WouldBlock)
438            } else {
439                self.track_egress_ip_tos(local_addr, endpoint);
440                // UDP allows zero-length payloads (IP header + UDP header only).
441                if payload_len == 0 {
442                    socket
443                        .send(
444                            0,
445                            UdpMetadata {
446                                endpoint,
447                                local_address: local_addr,
448                                meta: PacketMeta::default(),
449                            },
450                        )
451                        .map_err(|e| match e {
452                            smol::SendError::BufferFull => NetError::WouldBlock,
453                            smol::SendError::Unaddressable => NetError::ConnectionRefused,
454                        })?;
455                    *cork_guard = None;
456                    return Ok(0);
457                }
458                let buf = socket
459                    .send(
460                        payload_len,
461                        UdpMetadata {
462                            endpoint,
463                            local_address: local_addr,
464                            meta: PacketMeta::default(),
465                        },
466                    )
467                    .map_err(|e| match e {
468                        smol::SendError::BufferFull => NetError::WouldBlock,
469                        smol::SendError::Unaddressable => NetError::ConnectionRefused,
470                    })?;
471                let mut total_written = 0;
472                let mut cur_read = 0;
473                if let Some(ref c) = *cork_guard {
474                    let n = c.buf.len().min(buf.len());
475                    buf[..n].copy_from_slice(&c.buf[..n]);
476                    total_written += n;
477                }
478                if total_written < buf.len() {
479                    cur_read = src.read(&mut buf[total_written..])?;
480                    total_written += cur_read;
481                }
482                assert_eq!(total_written, buf.len());
483                // Success — clear cork state.
484                *cork_guard = None;
485                // Return only bytes consumed from the *current* user buffer.
486                Ok(cur_read)
487            }
488        })?;
489        request_poll();
490        Ok(result)
491    }
492
493    /// Receives one datagram while honoring peer filters and recv flags.
494    fn try_recv(&self, mut dst: impl Write, options: &mut RecvOptions) -> NetResult<usize> {
495        enum ExpectedRemote<'a> {
496            Any(&'a mut SocketAddrEx),
497            AnyDiscard,
498            Expecting(IpEndpoint),
499        }
500        let mut expected_remote = match options.from.as_deref_mut() {
501            Some(addr) => ExpectedRemote::Any(addr),
502            None => match self.remote_endpoint() {
503                Ok((endpoint, _)) => ExpectedRemote::Expecting(endpoint),
504                Err(_) => ExpectedRemote::AnyDiscard,
505            },
506        };
507
508        request_poll();
509        self.with_smol_socket(|socket| {
510            if !socket.can_recv() {
511                Err(NetError::WouldBlock)
512            } else {
513                let result = if options.flags.contains(RecvFlags::PEEK) {
514                    socket.peek().map(|(data, meta)| (data, *meta))
515                } else {
516                    socket.recv()
517                };
518                match result {
519                    Ok((src, meta)) => {
520                        match &mut expected_remote {
521                            ExpectedRemote::Any(remote_addr) => {
522                                **remote_addr = SocketAddrEx::Ip(meta.endpoint.into());
523                            }
524                            ExpectedRemote::AnyDiscard => {
525                                // recv() with no addr buffer and no peer — accept from any
526                            }
527                            ExpectedRemote::Expecting(expected) => {
528                                if (!expected.addr.is_unspecified()
529                                    && expected.addr != meta.endpoint.addr)
530                                    || (expected.port != 0 && expected.port != meta.endpoint.port)
531                                {
532                                    return Err(NetError::WouldBlock);
533                                }
534                            }
535                        }
536
537                        let read = dst.write(src)?;
538                        if read < src.len() {
539                            warn!("UDP message truncated: {} -> {} bytes", src.len(), read);
540                            if let Some(ref mut truncated) = options.truncated {
541                                **truncated = true;
542                            }
543                        }
544
545                        if let Some(cmsg) = options.cmsg.as_deref_mut()
546                            && let Some(traffic_class) = received_traffic_class(meta.meta)
547                        {
548                            match traffic_class {
549                                ReceivedTrafficClass::Ipv4(tos)
550                                    if self.general.recv_traffic_class() =>
551                                {
552                                    cmsg.push(Box::new(IpCmsg::Ipv6TrafficClass(tos)));
553                                }
554                                ReceivedTrafficClass::Ipv4(tos) if self.general.recv_tos() => {
555                                    cmsg.push(Box::new(IpCmsg::Ipv4Tos(tos)));
556                                }
557                                ReceivedTrafficClass::Ipv6(tclass)
558                                    if self.general.recv_traffic_class() =>
559                                {
560                                    cmsg.push(Box::new(IpCmsg::Ipv6TrafficClass(tclass)));
561                                }
562                                _ => {}
563                            }
564                        }
565
566                        Ok(if options.flags.contains(RecvFlags::TRUNCATE) {
567                            src.len()
568                        } else {
569                            read
570                        })
571                    }
572                    Err(smol::RecvError::Exhausted) => Err(NetError::WouldBlock),
573                    Err(smol::RecvError::Truncated) => {
574                        unreachable!("UDP socket recv never returns Err(Truncated)")
575                    }
576                }
577            }
578        })
579    }
580
581    fn local_addr(&self) -> NetResult<SocketAddrEx> {
582        match self.local_addr.try_lock() {
583            Some(addr) => addr
584                .map(Into::into)
585                .map(SocketAddrEx::Ip)
586                .ok_or(NetError::NotConnected),
587            None => Err(NetError::NotConnected),
588        }
589    }
590
591    fn peer_addr(&self) -> NetResult<SocketAddrEx> {
592        self.remote_endpoint()
593            .map(|it| it.0.into())
594            .map(SocketAddrEx::Ip)
595    }
596
597    fn shutdown(&self, _how: Shutdown) -> NetResult {
598        // TODO(mivik): shutdown
599        request_poll();
600
601        self.with_smol_socket(|socket| {
602            debug!("UDP socket {}: shutting down", self.handle);
603            socket.close();
604        });
605        Ok(())
606    }
607}
608
609impl Pollable for UdpSocket {
610    fn poll(&self) -> IoEvents {
611        request_poll();
612        let Some(local_addr) = self.local_addr.try_lock() else {
613            return IoEvents::empty();
614        };
615        if local_addr.is_none() {
616            return IoEvents::empty();
617        }
618        drop(local_addr);
619
620        let mut events = IoEvents::empty();
621        self.with_smol_socket(|socket| {
622            events.set(IoEvents::IN, socket.can_recv());
623            events.set(IoEvents::OUT, socket.can_send());
624        });
625        events
626    }
627
628    unsafe fn register_shared(&self, sink: &mut dyn SharedRegistrationSink, events: IoEvents) {
629        unsafe { sink.register_shared(&self.poll_state, events) };
630        self.arm_poll_sources(events);
631    }
632
633    unsafe fn register_exclusive(
634        &self,
635        sink: &mut dyn ExclusiveRegistrationSink,
636        events: IoEvents,
637    ) {
638        unsafe { sink.register_exclusive(&self.poll_state, events) };
639        self.arm_poll_sources(events);
640    }
641}
642
643impl UdpSocket {
644    fn arm_poll_sources(&self, events: IoEvents) {
645        self.with_smol_socket(|socket| {
646            if events.contains(IoEvents::IN) {
647                socket.register_recv_waker(&Waker::from(Arc::new(SocketDeferPollWake::new(
648                    self.poll_state.clone(),
649                    IoEvents::IN,
650                    self.readiness_version.clone(),
651                ))));
652            }
653            if events.contains(IoEvents::OUT) {
654                socket.register_send_waker(&Waker::from(Arc::new(SocketDeferPollWake::new(
655                    self.poll_state.clone(),
656                    IoEvents::OUT,
657                    self.readiness_version.clone(),
658                ))));
659            }
660        });
661        if events.intersects(IoEvents::IN | IoEvents::OUT) {
662            self.general
663                .register_waker(&Waker::from(Arc::new(SocketDeferPollWake::new(
664                    self.poll_state.clone(),
665                    events,
666                    self.readiness_version.clone(),
667                ))));
668        }
669    }
670}
671
672impl Default for UdpSocket {
673    fn default() -> Self {
674        Self::new()
675    }
676}
677
678impl Drop for UdpSocket {
679    fn drop(&mut self) {
680        // Dispatch any datagram still queued in the TX buffer to its destination
681        // while the socket is still open, so a send immediately followed by close
682        // is not lost (Linux keeps the datagram in the peer's receive buffer).
683        // smoltcp's `close()` drops the send buffer, so this must run before it.
684        flush_egress();
685        self.shutdown(Shutdown::Both).ok();
686        self.clear_tracked_egress_ip_tos();
687        SOCKET_SET.remove(self.handle);
688    }
689}
690
691fn get_ephemeral_port() -> NetResult<u16> {
692    allocate_ephemeral_port(|port| {
693        SOCKET_SET.udp_port_available(IpAddress::Ipv4(Ipv4Addr::UNSPECIFIED), port)
694    })
695}