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::sockets::{SocketAddressFamily, WasiSocketsCtxView};
6use async_trait::async_trait;
7use std::future::poll_fn;
8use std::net::SocketAddr;
9use std::task::{Context, Poll, Waker};
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    fn drop(&mut self, this: Resource<udp::IncomingDatagramStream>) -> Result<(), wasmtime::Error> {
221        // As in the filesystem implementation, we assume closing a socket
222        // doesn't block.
223        let dropped = self.table.delete(this)?;
224        drop(dropped);
225
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 Poll::Ready(()) =
242            stream.poll_send_ready(&mut Context::from_waker(Waker::noop()))
243        {
244            // We don't know how many Tokio will accept, so we make up a
245            // reasonable number here.  If we're wrong and `send` returns
246            // `Ok(0)`, the guest will just have to deal with that, e.g. by
247            // looping or returning `EWOULDBLOCK`.
248            MAX_DATAGRAMS
249        } else {
250            0
251        };
252
253        stream.check_send_permit_count = count;
254
255        Ok(count.try_into().unwrap())
256    }
257
258    fn send(
259        &mut self,
260        this: Resource<udp::OutgoingDatagramStream>,
261        datagrams: Vec<udp::OutgoingDatagram>,
262    ) -> SocketResult<u64> {
263        let stream = self.table.get_mut(&this)?;
264
265        if datagrams.is_empty() {
266            return Ok(0);
267        }
268
269        if datagrams.len() > stream.check_send_permit_count {
270            return Err(SocketError::trap(wasmtime::format_err!(
271                "unpermitted: argument exceeds permitted size"
272            )));
273        }
274
275        // Reset permit. From the WIT spec:
276        // > Each call to `send` must be permitted by a preceding `check-send`.
277        stream.check_send_permit_count = 0;
278
279        let mut count = 0;
280
281        for datagram in datagrams {
282            match stream.try_send(datagram.data, datagram.remote_address.map(SocketAddr::from)) {
283                Err(ErrorCode::WouldBlock) => break,
284                Ok(()) => count += 1,
285                Err(_) if count > 0 => {
286                    // WIT: "If at least one datagram has been sent successfully, this function never returns an error."
287                    break;
288                }
289                Err(e) => {
290                    return Err(e.into());
291                }
292            }
293        }
294
295        Ok(count)
296    }
297
298    fn subscribe(
299        &mut self,
300        this: Resource<udp::OutgoingDatagramStream>,
301    ) -> wasmtime::Result<Resource<DynPollable>> {
302        wasmtime_wasi_io::poll::subscribe(self.table, this)
303    }
304
305    async fn drop(
306        &mut self,
307        this: Resource<udp::OutgoingDatagramStream>,
308    ) -> Result<(), wasmtime::Error> {
309        let mut stream = self.table.delete(this)?;
310        // Prevent silently dropping already-acknowledged sends by waiting for
311        // any in-progress background send to complete. This may block
312        // the guest, but that's the price we pay for implementing the
313        // readiness-based P2 API in terms of the completion-based P3 API.
314        std::future::poll_fn(|cx| stream.poll_send_ready(cx)).await;
315        drop(stream);
316        Ok(())
317    }
318}
319
320#[async_trait]
321impl Pollable for OutgoingDatagramStream {
322    async fn ready(&mut self) {
323        poll_fn(|cx| self.poll_send_ready(cx)).await
324    }
325}
326
327impl From<SocketAddressFamily> for IpAddressFamily {
328    fn from(family: SocketAddressFamily) -> IpAddressFamily {
329        match family {
330            SocketAddressFamily::Ipv4 => IpAddressFamily::Ipv4,
331            SocketAddressFamily::Ipv6 => IpAddressFamily::Ipv6,
332        }
333    }
334}
335
336pub mod sync {
337    use wasmtime::component::Resource;
338
339    use crate::p2::{
340        SocketError, UdpSocket,
341        bindings::{
342            sockets::{
343                network::Network,
344                udp::{
345                    self as async_udp,
346                    HostIncomingDatagramStream as AsyncHostIncomingDatagramStream,
347                    HostOutgoingDatagramStream as AsyncHostOutgoingDatagramStream,
348                    HostUdpSocket as AsyncHostUdpSocket, IncomingDatagramStream,
349                    OutgoingDatagramStream,
350                },
351            },
352            sync::sockets::udp::{
353                self, HostIncomingDatagramStream, HostOutgoingDatagramStream, HostUdpSocket,
354                IncomingDatagram, IpAddressFamily, IpSocketAddress, OutgoingDatagram, Pollable,
355            },
356        },
357    };
358    use crate::runtime::in_tokio;
359    use crate::sockets::WasiSocketsCtxView;
360
361    impl udp::Host for WasiSocketsCtxView<'_> {}
362
363    impl HostUdpSocket for WasiSocketsCtxView<'_> {
364        fn start_bind(
365            &mut self,
366            self_: Resource<UdpSocket>,
367            network: Resource<Network>,
368            local_address: IpSocketAddress,
369        ) -> Result<(), SocketError> {
370            in_tokio(async {
371                AsyncHostUdpSocket::start_bind(self, self_, network, local_address).await
372            })
373        }
374
375        fn finish_bind(&mut self, self_: Resource<UdpSocket>) -> Result<(), SocketError> {
376            AsyncHostUdpSocket::finish_bind(self, self_)
377        }
378
379        fn stream(
380            &mut self,
381            self_: Resource<UdpSocket>,
382            remote_address: Option<IpSocketAddress>,
383        ) -> Result<
384            (
385                Resource<IncomingDatagramStream>,
386                Resource<OutgoingDatagramStream>,
387            ),
388            SocketError,
389        > {
390            in_tokio(async { AsyncHostUdpSocket::stream(self, self_, remote_address).await })
391        }
392
393        fn local_address(
394            &mut self,
395            self_: Resource<UdpSocket>,
396        ) -> Result<IpSocketAddress, SocketError> {
397            AsyncHostUdpSocket::local_address(self, self_)
398        }
399
400        fn remote_address(
401            &mut self,
402            self_: Resource<UdpSocket>,
403        ) -> Result<IpSocketAddress, SocketError> {
404            AsyncHostUdpSocket::remote_address(self, self_)
405        }
406
407        fn address_family(
408            &mut self,
409            self_: Resource<UdpSocket>,
410        ) -> wasmtime::Result<IpAddressFamily> {
411            AsyncHostUdpSocket::address_family(self, self_)
412        }
413
414        fn unicast_hop_limit(&mut self, self_: Resource<UdpSocket>) -> Result<u8, SocketError> {
415            AsyncHostUdpSocket::unicast_hop_limit(self, self_)
416        }
417
418        fn set_unicast_hop_limit(
419            &mut self,
420            self_: Resource<UdpSocket>,
421            value: u8,
422        ) -> Result<(), SocketError> {
423            AsyncHostUdpSocket::set_unicast_hop_limit(self, self_, value)
424        }
425
426        fn receive_buffer_size(&mut self, self_: Resource<UdpSocket>) -> Result<u64, SocketError> {
427            AsyncHostUdpSocket::receive_buffer_size(self, self_)
428        }
429
430        fn set_receive_buffer_size(
431            &mut self,
432            self_: Resource<UdpSocket>,
433            value: u64,
434        ) -> Result<(), SocketError> {
435            AsyncHostUdpSocket::set_receive_buffer_size(self, self_, value)
436        }
437
438        fn send_buffer_size(&mut self, self_: Resource<UdpSocket>) -> Result<u64, SocketError> {
439            AsyncHostUdpSocket::send_buffer_size(self, self_)
440        }
441
442        fn set_send_buffer_size(
443            &mut self,
444            self_: Resource<UdpSocket>,
445            value: u64,
446        ) -> Result<(), SocketError> {
447            AsyncHostUdpSocket::set_send_buffer_size(self, self_, value)
448        }
449
450        fn subscribe(
451            &mut self,
452            self_: Resource<UdpSocket>,
453        ) -> wasmtime::Result<Resource<Pollable>> {
454            AsyncHostUdpSocket::subscribe(self, self_)
455        }
456
457        fn drop(&mut self, rep: Resource<UdpSocket>) -> wasmtime::Result<()> {
458            AsyncHostUdpSocket::drop(self, rep)
459        }
460    }
461
462    impl HostIncomingDatagramStream for WasiSocketsCtxView<'_> {
463        fn receive(
464            &mut self,
465            self_: Resource<IncomingDatagramStream>,
466            max_results: u64,
467        ) -> Result<Vec<IncomingDatagram>, SocketError> {
468            Ok(
469                AsyncHostIncomingDatagramStream::receive(self, self_, max_results)?
470                    .into_iter()
471                    .map(Into::into)
472                    .collect(),
473            )
474        }
475
476        fn subscribe(
477            &mut self,
478            self_: Resource<IncomingDatagramStream>,
479        ) -> wasmtime::Result<Resource<Pollable>> {
480            AsyncHostIncomingDatagramStream::subscribe(self, self_)
481        }
482
483        fn drop(&mut self, rep: Resource<IncomingDatagramStream>) -> wasmtime::Result<()> {
484            AsyncHostIncomingDatagramStream::drop(self, rep)
485        }
486    }
487
488    impl From<async_udp::IncomingDatagram> for IncomingDatagram {
489        fn from(other: async_udp::IncomingDatagram) -> Self {
490            let async_udp::IncomingDatagram {
491                data,
492                remote_address,
493            } = other;
494            Self {
495                data,
496                remote_address,
497            }
498        }
499    }
500
501    impl HostOutgoingDatagramStream for WasiSocketsCtxView<'_> {
502        fn check_send(
503            &mut self,
504            self_: Resource<OutgoingDatagramStream>,
505        ) -> Result<u64, SocketError> {
506            AsyncHostOutgoingDatagramStream::check_send(self, self_)
507        }
508
509        fn send(
510            &mut self,
511            self_: Resource<OutgoingDatagramStream>,
512            datagrams: Vec<OutgoingDatagram>,
513        ) -> Result<u64, SocketError> {
514            let datagrams = datagrams.into_iter().map(Into::into).collect();
515            AsyncHostOutgoingDatagramStream::send(self, self_, datagrams)
516        }
517
518        fn subscribe(
519            &mut self,
520            self_: Resource<OutgoingDatagramStream>,
521        ) -> wasmtime::Result<Resource<Pollable>> {
522            AsyncHostOutgoingDatagramStream::subscribe(self, self_)
523        }
524
525        fn drop(&mut self, rep: Resource<OutgoingDatagramStream>) -> wasmtime::Result<()> {
526            in_tokio(async { AsyncHostOutgoingDatagramStream::drop(self, rep).await })
527        }
528    }
529
530    impl From<OutgoingDatagram> for async_udp::OutgoingDatagram {
531        fn from(other: OutgoingDatagram) -> Self {
532            let OutgoingDatagram {
533                data,
534                remote_address,
535            } = other;
536            Self {
537                data,
538                remote_address,
539            }
540        }
541    }
542}