Skip to main content

wasmtime_wasi/p2/host/
udp.rs

1use crate::p2::bindings::sockets::network::{ErrorCode, IpAddressFamily, IpSocketAddress, Network};
2use crate::p2::bindings::sockets::udp;
3use crate::p2::udp::{AsyncOperation, IncomingDatagramStream, OutgoingDatagramStream};
4use crate::p2::{Pollable, SocketError, SocketResult, UdpSocket};
5use crate::runtime::poll_now;
6use crate::sockets::{SocketAddressFamily, WasiSocketsCtxView};
7use async_trait::async_trait;
8use std::future::poll_fn;
9use std::net::SocketAddr;
10use wasmtime::component::Resource;
11use wasmtime::format_err;
12use wasmtime_wasi_io::poll::DynPollable;
13
14const MAX_DATAGRAMS: usize = 16;
15
16impl udp::Host for WasiSocketsCtxView<'_> {}
17
18impl udp::HostUdpSocket for WasiSocketsCtxView<'_> {
19    async fn start_bind(
20        &mut self,
21        this: Resource<UdpSocket>,
22        network: Resource<Network>,
23        local_address: IpSocketAddress,
24    ) -> SocketResult<()> {
25        // The network resource itself represents the capability to use this
26        // method, so we need to check its validity. Other than that, we have no
27        // use for it.
28        _ = self.table.get(&network)?;
29
30        let local_address = SocketAddr::from(local_address);
31        let socket = self.table.get_mut(&this)?;
32        if socket.in_progress_operation.is_some() {
33            return Err(ErrorCode::ConcurrencyConflict.into());
34        }
35
36        socket
37            .get_mut()
38            .ok_or(ErrorCode::InvalidState)?
39            .bind(local_address)
40            .await?;
41
42        socket.in_progress_operation = Some(AsyncOperation::Bind);
43        Ok(())
44    }
45
46    fn finish_bind(&mut self, this: Resource<UdpSocket>) -> SocketResult<()> {
47        let socket = self.table.get_mut(&this)?;
48        if socket.in_progress_operation != Some(AsyncOperation::Bind) {
49            return Err(ErrorCode::NotInProgress.into());
50        };
51        socket.in_progress_operation = None;
52        Ok(())
53    }
54
55    async fn stream(
56        &mut self,
57        this: Resource<UdpSocket>,
58        remote_address: Option<IpSocketAddress>,
59    ) -> SocketResult<(
60        Resource<udp::IncomingDatagramStream>,
61        Resource<udp::OutgoingDatagramStream>,
62    )> {
63        let has_active_streams = self
64            .table
65            .iter_children(&this)?
66            .any(|c| c.is::<IncomingDatagramStream>() || c.is::<OutgoingDatagramStream>());
67
68        if has_active_streams {
69            return Err(SocketError::trap(format_err!(
70                "UDP streams not dropped yet"
71            )));
72        }
73
74        let socket = self.table.get_mut(&this)?;
75        let inner = socket
76            .get_mut()
77            .ok_or(SocketError::trap(wasmtime::error::format_err!(
78                "`connect` needs exclusive access"
79            )))?;
80
81        if !inner.is_bound() {
82            // In WASI 0.2, sockets had to be explicitly bound before connecting.
83            return Err(ErrorCode::InvalidState.into());
84        }
85
86        if let Some(connect_addr) = remote_address {
87            inner.connect(connect_addr.into()).await?;
88        } else if inner.is_connected() {
89            inner.disconnect()?;
90        }
91        let incoming_stream = IncomingDatagramStream::new(socket.inner.clone());
92        let outgoing_stream = OutgoingDatagramStream::new(socket.inner.clone());
93        Ok((
94            self.table.push_child(incoming_stream, &this)?,
95            self.table.push_child(outgoing_stream, &this)?,
96        ))
97    }
98
99    fn local_address(&mut self, this: Resource<UdpSocket>) -> SocketResult<IpSocketAddress> {
100        let mut socket = self.table.get(&this)?.lock();
101        Ok(socket.local_address()?.into())
102    }
103
104    fn remote_address(&mut self, this: Resource<UdpSocket>) -> SocketResult<IpSocketAddress> {
105        let mut socket = self.table.get(&this)?.lock();
106        Ok(socket.remote_address()?.into())
107    }
108
109    fn address_family(
110        &mut self,
111        this: Resource<UdpSocket>,
112    ) -> Result<IpAddressFamily, wasmtime::Error> {
113        let socket = self.table.get(&this)?.lock();
114        Ok(socket.address_family().into())
115    }
116
117    fn unicast_hop_limit(&mut self, this: Resource<UdpSocket>) -> SocketResult<u8> {
118        let socket = self.table.get(&this)?.lock();
119        Ok(socket.unicast_hop_limit()?)
120    }
121
122    fn set_unicast_hop_limit(&mut self, this: Resource<UdpSocket>, value: u8) -> SocketResult<()> {
123        let socket = self.table.get(&this)?.lock();
124        socket.set_unicast_hop_limit(value)?;
125        Ok(())
126    }
127
128    fn receive_buffer_size(&mut self, this: Resource<UdpSocket>) -> SocketResult<u64> {
129        let socket = self.table.get(&this)?.lock();
130        Ok(socket.receive_buffer_size()?)
131    }
132
133    fn set_receive_buffer_size(
134        &mut self,
135        this: Resource<UdpSocket>,
136        value: u64,
137    ) -> SocketResult<()> {
138        let socket = self.table.get(&this)?.lock();
139        socket.set_receive_buffer_size(value)?;
140        Ok(())
141    }
142
143    fn send_buffer_size(&mut self, this: Resource<UdpSocket>) -> SocketResult<u64> {
144        let socket = self.table.get(&this)?.lock();
145        Ok(socket.send_buffer_size()?)
146    }
147
148    fn set_send_buffer_size(&mut self, this: Resource<UdpSocket>, value: u64) -> SocketResult<()> {
149        let socket = self.table.get(&this)?.lock();
150        socket.set_send_buffer_size(value)?;
151        Ok(())
152    }
153
154    fn subscribe(&mut self, this: Resource<UdpSocket>) -> wasmtime::Result<Resource<DynPollable>> {
155        wasmtime_wasi_io::poll::subscribe(self.table, this)
156    }
157
158    fn drop(&mut self, this: Resource<UdpSocket>) -> Result<(), wasmtime::Error> {
159        // As in the filesystem implementation, we assume closing a socket
160        // doesn't block.
161        let dropped = self.table.delete(this)?;
162        drop(dropped);
163
164        Ok(())
165    }
166}
167
168#[async_trait]
169impl Pollable for UdpSocket {
170    async fn ready(&mut self) {
171        // None of the socket-level operations block natively
172    }
173}
174
175impl udp::HostIncomingDatagramStream for WasiSocketsCtxView<'_> {
176    fn receive(
177        &mut self,
178        this: Resource<udp::IncomingDatagramStream>,
179        max_results: u64,
180    ) -> SocketResult<Vec<udp::IncomingDatagram>> {
181        let stream = self.table.get_mut(&this)?;
182        let max_results: usize = max_results
183            .try_into()
184            .unwrap_or(usize::MAX)
185            .min(MAX_DATAGRAMS);
186        if max_results == 0 {
187            return Ok(vec![]);
188        }
189
190        let mut datagrams = vec![];
191        let mut sum = 0;
192
193        while datagrams.len() < max_results && sum < crate::MAX_READ_SIZE_ALLOC {
194            match stream.try_recv() {
195                Err(ErrorCode::WouldBlock) => break,
196                Ok((data, remote_addr)) => {
197                    sum += 1 + data.len();
198                    datagrams.push(udp::IncomingDatagram {
199                        data,
200                        remote_address: remote_addr.into(),
201                    });
202                }
203                Err(_) if datagrams.len() > 0 => break,
204                Err(e) => {
205                    return Err(e.into());
206                }
207            }
208        }
209
210        Ok(datagrams)
211    }
212
213    fn subscribe(
214        &mut self,
215        this: Resource<udp::IncomingDatagramStream>,
216    ) -> wasmtime::Result<Resource<DynPollable>> {
217        wasmtime_wasi_io::poll::subscribe(self.table, this)
218    }
219
220    async fn drop(
221        &mut self,
222        this: Resource<udp::IncomingDatagramStream>,
223    ) -> Result<(), wasmtime::Error> {
224        let dropped = self.table.delete(this)?;
225        dropped.finish().await;
226        Ok(())
227    }
228}
229
230#[async_trait]
231impl Pollable for IncomingDatagramStream {
232    async fn ready(&mut self) {
233        poll_fn(|cx| self.poll_recv_ready(cx)).await
234    }
235}
236
237impl udp::HostOutgoingDatagramStream for WasiSocketsCtxView<'_> {
238    fn check_send(&mut self, this: Resource<udp::OutgoingDatagramStream>) -> SocketResult<u64> {
239        let stream = self.table.get_mut(&this)?;
240
241        let count = if let Some(()) = poll_now(|cx| stream.poll_send_ready(cx)) {
242            // We don't know how many Tokio will accept, so we make up a
243            // reasonable number here.  If we're wrong and `send` returns
244            // `Ok(0)`, the guest will just have to deal with that, e.g. by
245            // looping or returning `EWOULDBLOCK`.
246            MAX_DATAGRAMS
247        } else {
248            0
249        };
250
251        stream.check_send_permit_count = count;
252
253        Ok(count.try_into().unwrap())
254    }
255
256    fn send(
257        &mut self,
258        this: Resource<udp::OutgoingDatagramStream>,
259        datagrams: Vec<udp::OutgoingDatagram>,
260    ) -> SocketResult<u64> {
261        let stream = self.table.get_mut(&this)?;
262
263        if datagrams.is_empty() {
264            return Ok(0);
265        }
266
267        if datagrams.len() > stream.check_send_permit_count {
268            return Err(SocketError::trap(wasmtime::format_err!(
269                "unpermitted: argument exceeds permitted size"
270            )));
271        }
272
273        // Reset permit. From the WIT spec:
274        // > Each call to `send` must be permitted by a preceding `check-send`.
275        stream.check_send_permit_count = 0;
276
277        let mut count = 0;
278
279        for datagram in datagrams {
280            match stream.try_send(datagram.data, datagram.remote_address.map(SocketAddr::from)) {
281                Err(ErrorCode::WouldBlock) => break,
282                Ok(()) => count += 1,
283                Err(_) if count > 0 => {
284                    // WIT: "If at least one datagram has been sent successfully, this function never returns an error."
285                    break;
286                }
287                Err(e) => {
288                    return Err(e.into());
289                }
290            }
291        }
292
293        Ok(count)
294    }
295
296    fn subscribe(
297        &mut self,
298        this: Resource<udp::OutgoingDatagramStream>,
299    ) -> wasmtime::Result<Resource<DynPollable>> {
300        wasmtime_wasi_io::poll::subscribe(self.table, this)
301    }
302
303    async fn drop(
304        &mut self,
305        this: Resource<udp::OutgoingDatagramStream>,
306    ) -> Result<(), wasmtime::Error> {
307        let mut stream = self.table.delete(this)?;
308        // Prevent silently dropping already-acknowledged sends by waiting for
309        // any in-progress background send to complete. This may block
310        // the guest, but that's the price we pay for implementing the
311        // readiness-based P2 API in terms of the completion-based P3 API.
312        std::future::poll_fn(|cx| stream.poll_send_ready(cx)).await;
313        drop(stream);
314        Ok(())
315    }
316}
317
318#[async_trait]
319impl Pollable for OutgoingDatagramStream {
320    async fn ready(&mut self) {
321        poll_fn(|cx| self.poll_send_ready(cx)).await
322    }
323}
324
325impl From<SocketAddressFamily> for IpAddressFamily {
326    fn from(family: SocketAddressFamily) -> IpAddressFamily {
327        match family {
328            SocketAddressFamily::Ipv4 => IpAddressFamily::Ipv4,
329            SocketAddressFamily::Ipv6 => IpAddressFamily::Ipv6,
330        }
331    }
332}
333
334pub mod sync {
335    use wasmtime::component::Resource;
336
337    use crate::p2::{
338        SocketError, UdpSocket,
339        bindings::{
340            sockets::{
341                network::Network,
342                udp::{
343                    self as async_udp,
344                    HostIncomingDatagramStream as AsyncHostIncomingDatagramStream,
345                    HostOutgoingDatagramStream as AsyncHostOutgoingDatagramStream,
346                    HostUdpSocket as AsyncHostUdpSocket, IncomingDatagramStream,
347                    OutgoingDatagramStream,
348                },
349            },
350            sync::sockets::udp::{
351                self, HostIncomingDatagramStream, HostOutgoingDatagramStream, HostUdpSocket,
352                IncomingDatagram, IpAddressFamily, IpSocketAddress, OutgoingDatagram, Pollable,
353            },
354        },
355    };
356    use crate::runtime::in_tokio;
357    use crate::sockets::WasiSocketsCtxView;
358
359    impl udp::Host for WasiSocketsCtxView<'_> {}
360
361    impl HostUdpSocket for WasiSocketsCtxView<'_> {
362        fn start_bind(
363            &mut self,
364            self_: Resource<UdpSocket>,
365            network: Resource<Network>,
366            local_address: IpSocketAddress,
367        ) -> Result<(), SocketError> {
368            in_tokio(async {
369                AsyncHostUdpSocket::start_bind(self, self_, network, local_address).await
370            })
371        }
372
373        fn finish_bind(&mut self, self_: Resource<UdpSocket>) -> Result<(), SocketError> {
374            AsyncHostUdpSocket::finish_bind(self, self_)
375        }
376
377        fn stream(
378            &mut self,
379            self_: Resource<UdpSocket>,
380            remote_address: Option<IpSocketAddress>,
381        ) -> Result<
382            (
383                Resource<IncomingDatagramStream>,
384                Resource<OutgoingDatagramStream>,
385            ),
386            SocketError,
387        > {
388            in_tokio(async { AsyncHostUdpSocket::stream(self, self_, remote_address).await })
389        }
390
391        fn local_address(
392            &mut self,
393            self_: Resource<UdpSocket>,
394        ) -> Result<IpSocketAddress, SocketError> {
395            AsyncHostUdpSocket::local_address(self, self_)
396        }
397
398        fn remote_address(
399            &mut self,
400            self_: Resource<UdpSocket>,
401        ) -> Result<IpSocketAddress, SocketError> {
402            AsyncHostUdpSocket::remote_address(self, self_)
403        }
404
405        fn address_family(
406            &mut self,
407            self_: Resource<UdpSocket>,
408        ) -> wasmtime::Result<IpAddressFamily> {
409            AsyncHostUdpSocket::address_family(self, self_)
410        }
411
412        fn unicast_hop_limit(&mut self, self_: Resource<UdpSocket>) -> Result<u8, SocketError> {
413            AsyncHostUdpSocket::unicast_hop_limit(self, self_)
414        }
415
416        fn set_unicast_hop_limit(
417            &mut self,
418            self_: Resource<UdpSocket>,
419            value: u8,
420        ) -> Result<(), SocketError> {
421            AsyncHostUdpSocket::set_unicast_hop_limit(self, self_, value)
422        }
423
424        fn receive_buffer_size(&mut self, self_: Resource<UdpSocket>) -> Result<u64, SocketError> {
425            AsyncHostUdpSocket::receive_buffer_size(self, self_)
426        }
427
428        fn set_receive_buffer_size(
429            &mut self,
430            self_: Resource<UdpSocket>,
431            value: u64,
432        ) -> Result<(), SocketError> {
433            AsyncHostUdpSocket::set_receive_buffer_size(self, self_, value)
434        }
435
436        fn send_buffer_size(&mut self, self_: Resource<UdpSocket>) -> Result<u64, SocketError> {
437            AsyncHostUdpSocket::send_buffer_size(self, self_)
438        }
439
440        fn set_send_buffer_size(
441            &mut self,
442            self_: Resource<UdpSocket>,
443            value: u64,
444        ) -> Result<(), SocketError> {
445            AsyncHostUdpSocket::set_send_buffer_size(self, self_, value)
446        }
447
448        fn subscribe(
449            &mut self,
450            self_: Resource<UdpSocket>,
451        ) -> wasmtime::Result<Resource<Pollable>> {
452            AsyncHostUdpSocket::subscribe(self, self_)
453        }
454
455        fn drop(&mut self, rep: Resource<UdpSocket>) -> wasmtime::Result<()> {
456            AsyncHostUdpSocket::drop(self, rep)
457        }
458    }
459
460    impl HostIncomingDatagramStream for WasiSocketsCtxView<'_> {
461        fn receive(
462            &mut self,
463            self_: Resource<IncomingDatagramStream>,
464            max_results: u64,
465        ) -> Result<Vec<IncomingDatagram>, SocketError> {
466            Ok(
467                AsyncHostIncomingDatagramStream::receive(self, self_, max_results)?
468                    .into_iter()
469                    .map(Into::into)
470                    .collect(),
471            )
472        }
473
474        fn subscribe(
475            &mut self,
476            self_: Resource<IncomingDatagramStream>,
477        ) -> wasmtime::Result<Resource<Pollable>> {
478            AsyncHostIncomingDatagramStream::subscribe(self, self_)
479        }
480
481        fn drop(&mut self, rep: Resource<IncomingDatagramStream>) -> wasmtime::Result<()> {
482            in_tokio(async { AsyncHostIncomingDatagramStream::drop(self, rep).await })
483        }
484    }
485
486    impl From<async_udp::IncomingDatagram> for IncomingDatagram {
487        fn from(other: async_udp::IncomingDatagram) -> Self {
488            let async_udp::IncomingDatagram {
489                data,
490                remote_address,
491            } = other;
492            Self {
493                data,
494                remote_address,
495            }
496        }
497    }
498
499    impl HostOutgoingDatagramStream for WasiSocketsCtxView<'_> {
500        fn check_send(
501            &mut self,
502            self_: Resource<OutgoingDatagramStream>,
503        ) -> Result<u64, SocketError> {
504            AsyncHostOutgoingDatagramStream::check_send(self, self_)
505        }
506
507        fn send(
508            &mut self,
509            self_: Resource<OutgoingDatagramStream>,
510            datagrams: Vec<OutgoingDatagram>,
511        ) -> Result<u64, SocketError> {
512            let datagrams = datagrams.into_iter().map(Into::into).collect();
513            AsyncHostOutgoingDatagramStream::send(self, self_, datagrams)
514        }
515
516        fn subscribe(
517            &mut self,
518            self_: Resource<OutgoingDatagramStream>,
519        ) -> wasmtime::Result<Resource<Pollable>> {
520            AsyncHostOutgoingDatagramStream::subscribe(self, self_)
521        }
522
523        fn drop(&mut self, rep: Resource<OutgoingDatagramStream>) -> wasmtime::Result<()> {
524            in_tokio(async { AsyncHostOutgoingDatagramStream::drop(self, rep).await })
525        }
526    }
527
528    impl From<OutgoingDatagram> for async_udp::OutgoingDatagram {
529        fn from(other: OutgoingDatagram) -> Self {
530            let OutgoingDatagram {
531                data,
532                remote_address,
533            } = other;
534            Self {
535                data,
536                remote_address,
537            }
538        }
539    }
540}