1use crate::runtime::with_ambient_tokio_runtime;
2use crate::sockets::{
3 ErrorCode, SocketAddrCheck, SocketAddrUse, SocketAddressFamily, WasiSocketsCtx,
4 get_receive_buffer_size, get_send_buffer_size, get_unicast_hop_limit, is_valid_address_family,
5 is_valid_remote_address, set_receive_buffer_size, set_send_buffer_size, set_unicast_hop_limit,
6 unspecified_addr,
7};
8use rustix::fd::AsFd;
9use rustix::io::Errno;
10use std::net::SocketAddr;
11use std::sync::Arc;
12use tracing::debug;
13
14pub(crate) const MAX_DATAGRAM_SIZE: usize = u16::MAX as usize;
18
19pub struct UdpSocket {
24 socket: Arc<tokio::net::UdpSocket>,
25 family: SocketAddressFamily,
26
27 permissions: SocketAddrCheck,
29
30 is_bound: bool,
33
34 remote_addr: Option<SocketAddr>,
37}
38
39impl UdpSocket {
40 pub(crate) async fn new(
42 cx: &WasiSocketsCtx,
43 family: SocketAddressFamily,
44 ) -> Result<Self, ErrorCode> {
45 cx.allowed_network_uses.check_allowed_udp()?;
46
47 let socket = with_ambient_tokio_runtime(|| socket(family))?;
48
49 socket.writable().await?;
59
60 Ok(Self {
61 socket: Arc::new(socket),
62 is_bound: false,
63 remote_addr: None,
64 permissions: cx.socket_addr_check.clone(),
65 family,
66 })
67 }
68
69 pub(crate) fn is_bound(&mut self) -> bool {
70 if !self.is_bound {
74 self.is_bound = self
75 .socket
76 .local_addr()
77 .is_ok_and(|addr| addr != unspecified_addr(self.family));
78 }
79 self.is_bound
80 }
81
82 pub(crate) fn local_address(&mut self) -> Result<SocketAddr, ErrorCode> {
83 if !self.is_bound() {
84 return Err(ErrorCode::InvalidState);
85 }
86 self.socket.local_addr().map_err(|e| e.into())
87 }
88
89 pub(crate) fn remote_address(&mut self) -> Result<SocketAddr, ErrorCode> {
90 self.remote_addr.ok_or(ErrorCode::InvalidState)
91 }
92
93 pub(crate) fn is_connected(&mut self) -> bool {
94 self.remote_addr.is_some()
95 }
96
97 pub(crate) async fn bind(&mut self, addr: SocketAddr) -> Result<(), ErrorCode> {
98 if self.is_bound() {
99 return Err(ErrorCode::InvalidState);
100 }
101 if !is_valid_address_family(addr.ip(), self.family) {
102 return Err(ErrorCode::InvalidArgument);
103 }
104
105 self.permissions.check(addr, SocketAddrUse::UdpBind).await?;
106
107 bind(&self.socket, addr)?;
108 Ok(())
109 }
110
111 pub(crate) async fn connect(&mut self, addr: SocketAddr) -> Result<(), ErrorCode> {
112 if !is_valid_address_family(addr.ip(), self.family) || !is_valid_remote_address(addr) {
113 return Err(ErrorCode::InvalidArgument);
114 }
115
116 {
118 if !self.is_bound() {
119 let implicit_bind_addr = unspecified_addr(self.family);
122 self.permissions
123 .check(implicit_bind_addr, SocketAddrUse::UdpBind)
124 .await?;
125 }
126
127 self.permissions.check(addr, SocketAddrUse::UdpSend).await?;
132 self.permissions
133 .check(addr, SocketAddrUse::UdpReceive)
134 .await?;
135 }
136
137 let result = connect(&self.socket, addr);
138 self.update_remote_address();
139 result.map_err(|e| e.into())
140 }
141
142 pub(crate) fn disconnect(&mut self) -> Result<(), ErrorCode> {
143 if !self.is_connected() {
144 return Err(ErrorCode::InvalidState);
145 }
146
147 self.is_bound = true;
154
155 let result = disconnect(&self.socket);
156 self.update_remote_address();
157 result.map_err(|e| e.into())
158 }
159
160 fn update_remote_address(&mut self) {
164 self.remote_addr = if let Ok(addr) = self.socket.peer_addr()
165 && addr != unspecified_addr(self.family)
166 {
167 Some(addr)
168 } else {
169 None
170 }
171 }
172
173 pub(crate) fn send(
174 &mut self,
175 data: Vec<u8>,
176 addr: Option<SocketAddr>,
177 ) -> impl Future<Output = Result<(), ErrorCode>> + Send + use<> {
178 let family = self.family;
179 let socket = self.socket.clone();
180 let permissions = self.permissions.clone();
181 let connected_addr = self.remote_address().ok();
182 let is_bound = self.is_bound();
183
184 async move {
185 if data.len() > MAX_DATAGRAM_SIZE {
186 return Err(ErrorCode::DatagramTooLarge);
187 }
188
189 let effective_addr = if let Some(addr) = addr {
190 if !is_valid_remote_address(addr) || !is_valid_address_family(addr.ip(), family) {
191 return Err(ErrorCode::InvalidArgument);
192 }
193
194 if connected_addr.is_some() && connected_addr != Some(addr) {
197 return Err(ErrorCode::InvalidArgument);
198 }
199
200 addr
201 } else if let Some(connected_addr) = connected_addr {
202 connected_addr
203 } else {
204 return Err(ErrorCode::InvalidArgument);
205 };
206
207 {
209 if !is_bound {
210 let implicit_bind_addr = unspecified_addr(family);
213 permissions
214 .check(implicit_bind_addr, SocketAddrUse::UdpBind)
215 .await?;
216 }
217
218 permissions
219 .check(effective_addr, SocketAddrUse::UdpSend)
220 .await?;
221 }
222
223 if connected_addr == Some(effective_addr) {
224 socket.send(&data).await?;
225 } else {
226 socket.send_to(&data, effective_addr).await?;
227 }
228
229 Ok(())
230 }
231 }
232
233 pub(crate) fn recv(
234 &mut self,
235 ) -> impl Future<Output = Result<(Vec<u8>, SocketAddr), ErrorCode>> + Send + use<> {
236 let socket = self.socket.clone();
237 let permissions = self.permissions.clone();
238 let is_bound = self.is_bound();
239
240 async move {
241 if !is_bound {
242 return Err(ErrorCode::InvalidState);
243 }
244
245 loop {
246 let mut data = vec![0; MAX_DATAGRAM_SIZE];
247 let (len, addr) = socket.recv_from(&mut data).await?;
248 data.truncate(len);
249
250 match permissions.check(addr, SocketAddrUse::UdpReceive).await {
251 Ok(()) => return Ok((data, addr)),
252 Err(_) => {
253 continue;
255 }
256 }
257 }
258 }
259 }
260
261 pub(crate) fn address_family(&self) -> SocketAddressFamily {
262 self.family
263 }
264
265 pub(crate) fn unicast_hop_limit(&self) -> Result<u8, ErrorCode> {
266 let n = get_unicast_hop_limit(&self.socket, self.family)?;
267 Ok(n)
268 }
269
270 pub(crate) fn set_unicast_hop_limit(&self, value: u8) -> Result<(), ErrorCode> {
271 set_unicast_hop_limit(&self.socket, self.family, value)?;
272 Ok(())
273 }
274
275 pub(crate) fn receive_buffer_size(&self) -> Result<u64, ErrorCode> {
276 let n = get_receive_buffer_size(&self.socket)?;
277 Ok(n)
278 }
279
280 pub(crate) fn set_receive_buffer_size(&self, value: u64) -> Result<(), ErrorCode> {
281 set_receive_buffer_size(&self.socket, value)?;
282 Ok(())
283 }
284
285 pub(crate) fn send_buffer_size(&self) -> Result<u64, ErrorCode> {
286 let n = get_send_buffer_size(&self.socket)?;
287 Ok(n)
288 }
289
290 pub(crate) fn set_send_buffer_size(&self, value: u64) -> Result<(), ErrorCode> {
291 set_send_buffer_size(&self.socket, value)?;
292 Ok(())
293 }
294}
295
296fn socket(family: SocketAddressFamily) -> std::io::Result<tokio::net::UdpSocket> {
298 #[cfg(windows)]
300 static INIT: std::sync::Once = std::sync::Once::new();
301 #[cfg(windows)]
302 INIT.call_once(|| {
303 let _ = std::net::TcpStream::connect(std::net::SocketAddrV4::new(
304 std::net::Ipv4Addr::UNSPECIFIED,
305 0,
306 ));
307 });
308
309 #[cfg(not(any(windows, target_vendor = "apple")))]
310 let flags = rustix::net::SocketFlags::CLOEXEC | rustix::net::SocketFlags::NONBLOCK;
311 #[cfg(any(windows, target_vendor = "apple"))]
312 let flags = rustix::net::SocketFlags::empty();
313
314 let socket = rustix::net::socket_with(
315 match family {
316 SocketAddressFamily::Ipv4 => rustix::net::AddressFamily::INET,
317 SocketAddressFamily::Ipv6 => rustix::net::AddressFamily::INET6,
318 },
319 rustix::net::SocketType::DGRAM,
320 flags,
321 None,
322 )?;
323 #[cfg(target_vendor = "apple")]
324 rustix::io::ioctl_fioclex(&socket)?;
325 #[cfg(any(windows, target_vendor = "apple"))]
326 rustix::io::ioctl_fionbio(&socket, true)?;
327
328 if family == SocketAddressFamily::Ipv6 {
332 rustix::net::sockopt::set_ipv6_v6only(&socket, true)?;
333 }
334
335 Ok(tokio::net::UdpSocket::try_from(std::net::UdpSocket::from(
336 socket,
337 ))?)
338}
339
340fn bind(sockfd: impl AsFd, addr: SocketAddr) -> Result<(), Errno> {
341 rustix::net::bind(sockfd, &addr).map_err(|err| match err {
342 #[cfg(windows)]
345 Errno::NOBUFS => Errno::ADDRINUSE,
346 Errno::AFNOSUPPORT => Errno::INVAL,
353 _ => err,
354 })
355}
356
357fn connect(sockfd: impl AsFd, addr: SocketAddr) -> Result<(), Errno> {
358 match rustix::net::connect(sockfd.as_fd(), &addr) {
359 #[cfg(target_os = "linux")]
369 Err(Errno::INVAL) => {
370 _ = disconnect(sockfd.as_fd());
371 return rustix::net::connect(sockfd.as_fd(), &addr);
372 }
373 Err(Errno::AFNOSUPPORT) => Err(Errno::INVAL),
378 Err(Errno::INPROGRESS) => {
381 debug!("UDP connect returned EINPROGRESS, which should never happen");
382 Ok(())
383 }
384 r => r,
385 }
386}
387
388fn disconnect(sockfd: impl AsFd) -> Result<(), Errno> {
389 match rustix::net::connect_unspec(sockfd) {
390 #[cfg(target_os = "macos")]
402 Err(Errno::INVAL | Errno::AFNOSUPPORT) => Ok(()),
403 r => r,
404 }
405}