@@ -2,8 +2,8 @@ use crate::p2::bindings::sockets::network::{
22 self , ErrorCode , IpAddress , IpAddressFamily , IpSocketAddress , Ipv4SocketAddress ,
33 Ipv6SocketAddress ,
44} ;
5- use crate :: p2:: network:: { from_ipv4_addr, from_ipv6_addr, to_ipv4_addr, to_ipv6_addr} ;
65use crate :: p2:: { IoView , SocketError , WasiImpl , WasiView } ;
6+ use crate :: sockets:: util:: { from_ipv4_addr, from_ipv6_addr, to_ipv4_addr, to_ipv6_addr} ;
77use anyhow:: Error ;
88use rustix:: io:: Errno ;
99use std:: io;
@@ -242,331 +242,3 @@ impl From<cap_net_ext::AddressFamily> for IpAddressFamily {
242242 }
243243 }
244244}
245-
246- pub ( crate ) mod util {
247- use std:: io;
248- use std:: net:: { IpAddr , Ipv6Addr , SocketAddr } ;
249- use std:: time:: Duration ;
250-
251- use crate :: sockets:: SocketAddressFamily ;
252- use cap_net_ext:: { AddressFamily , Blocking , UdpSocketExt } ;
253- use rustix:: fd:: { AsFd , OwnedFd } ;
254- use rustix:: io:: Errno ;
255- use rustix:: net:: sockopt;
256-
257- pub fn validate_unicast ( addr : & SocketAddr ) -> io:: Result < ( ) > {
258- match to_canonical ( & addr. ip ( ) ) {
259- IpAddr :: V4 ( ipv4) => {
260- if ipv4. is_multicast ( ) || ipv4. is_broadcast ( ) {
261- Err ( io:: Error :: new (
262- io:: ErrorKind :: InvalidInput ,
263- "Both IPv4 broadcast and multicast addresses are not supported" ,
264- ) )
265- } else {
266- Ok ( ( ) )
267- }
268- }
269- IpAddr :: V6 ( ipv6) => {
270- if ipv6. is_multicast ( ) {
271- Err ( io:: Error :: new (
272- io:: ErrorKind :: InvalidInput ,
273- "IPv6 multicast addresses are not supported" ,
274- ) )
275- } else {
276- Ok ( ( ) )
277- }
278- }
279- }
280- }
281-
282- pub fn validate_remote_address ( addr : & SocketAddr ) -> io:: Result < ( ) > {
283- if to_canonical ( & addr. ip ( ) ) . is_unspecified ( ) {
284- return Err ( io:: Error :: new (
285- io:: ErrorKind :: InvalidInput ,
286- "Remote address may not be `0.0.0.0` or `::`" ,
287- ) ) ;
288- }
289-
290- if addr. port ( ) == 0 {
291- return Err ( io:: Error :: new (
292- io:: ErrorKind :: InvalidInput ,
293- "Remote port may not be 0" ,
294- ) ) ;
295- }
296-
297- Ok ( ( ) )
298- }
299-
300- pub fn validate_address_family (
301- addr : & SocketAddr ,
302- socket_family : & SocketAddressFamily ,
303- ) -> io:: Result < ( ) > {
304- match ( socket_family, addr. ip ( ) ) {
305- ( SocketAddressFamily :: Ipv4 , IpAddr :: V4 ( _) ) => Ok ( ( ) ) ,
306- ( SocketAddressFamily :: Ipv6 , IpAddr :: V6 ( ipv6) ) => {
307- if is_deprecated_ipv4_compatible ( & ipv6) {
308- // Reject IPv4-*compatible* IPv6 addresses. They have been deprecated
309- // since 2006, OS handling of them is inconsistent and our own
310- // validations don't take them into account either.
311- // Note that these are not the same as IPv4-*mapped* IPv6 addresses.
312- Err ( io:: Error :: new (
313- io:: ErrorKind :: InvalidInput ,
314- "IPv4-compatible IPv6 addresses are not supported" ,
315- ) )
316- } else if ipv6. to_ipv4_mapped ( ) . is_some ( ) {
317- Err ( io:: Error :: new (
318- io:: ErrorKind :: InvalidInput ,
319- "IPv4-mapped IPv6 address passed to an IPv6-only socket" ,
320- ) )
321- } else {
322- Ok ( ( ) )
323- }
324- }
325- _ => Err ( io:: Error :: new (
326- io:: ErrorKind :: InvalidInput ,
327- "Address family mismatch" ,
328- ) ) ,
329- }
330- }
331-
332- // Can be removed once `IpAddr::to_canonical` becomes stable.
333- pub fn to_canonical ( addr : & IpAddr ) -> IpAddr {
334- match addr {
335- IpAddr :: V4 ( ipv4) => IpAddr :: V4 ( * ipv4) ,
336- IpAddr :: V6 ( ipv6) => {
337- if let Some ( ipv4) = ipv6. to_ipv4_mapped ( ) {
338- IpAddr :: V4 ( ipv4)
339- } else {
340- IpAddr :: V6 ( * ipv6)
341- }
342- }
343- }
344- }
345-
346- fn is_deprecated_ipv4_compatible ( addr : & Ipv6Addr ) -> bool {
347- matches ! ( addr. segments( ) , [ 0 , 0 , 0 , 0 , 0 , 0 , _, _] )
348- && * addr != Ipv6Addr :: UNSPECIFIED
349- && * addr != Ipv6Addr :: LOCALHOST
350- }
351-
352- /*
353- * Syscalls wrappers with (opinionated) portability fixes.
354- */
355-
356- pub fn udp_socket ( family : AddressFamily , blocking : Blocking ) -> io:: Result < OwnedFd > {
357- // Delegate socket creation to cap_net_ext. They handle a couple of things for us:
358- // - On Windows: call WSAStartup if not done before.
359- // - Set the NONBLOCK and CLOEXEC flags. Either immediately during socket creation,
360- // or afterwards using ioctl or fcntl. Exact method depends on the platform.
361-
362- let socket = cap_std:: net:: UdpSocket :: new ( family, blocking) ?;
363- Ok ( OwnedFd :: from ( socket) )
364- }
365-
366- pub fn udp_bind < Fd : AsFd > ( sockfd : Fd , addr : & SocketAddr ) -> rustix:: io:: Result < ( ) > {
367- rustix:: net:: bind ( sockfd, addr) . map_err ( |error| match error {
368- // See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind#:~:text=WSAENOBUFS
369- // Windows returns WSAENOBUFS when the ephemeral ports have been exhausted.
370- #[ cfg( windows) ]
371- Errno :: NOBUFS => Errno :: ADDRINUSE ,
372- _ => error,
373- } )
374- }
375-
376- pub fn udp_disconnect < Fd : AsFd > ( sockfd : Fd ) -> rustix:: io:: Result < ( ) > {
377- match rustix:: net:: connect_unspec ( sockfd) {
378- // BSD platforms return an error even if the UDP socket was disconnected successfully.
379- //
380- // MacOS was kind enough to document this: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/connect.2.html
381- // > Datagram sockets may dissolve the association by connecting to an
382- // > invalid address, such as a null address or an address with the address
383- // > family set to AF_UNSPEC (the error EAFNOSUPPORT will be harmlessly
384- // > returned).
385- //
386- // ... except that this appears to be incomplete, because experiments
387- // have shown that MacOS actually returns EINVAL, depending on the
388- // address family of the socket.
389- #[ cfg( target_os = "macos" ) ]
390- Err ( Errno :: INVAL | Errno :: AFNOSUPPORT ) => Ok ( ( ) ) ,
391- r => r,
392- }
393- }
394-
395- // Even though SO_REUSEADDR is a SOL_* level option, this function contain a
396- // compatibility fix specific to TCP. That's why it contains the `_tcp_` infix instead of `_socket_`.
397- pub fn set_tcp_reuseaddr < Fd : AsFd > ( sockfd : Fd , value : bool ) -> rustix:: io:: Result < ( ) > {
398- // When a TCP socket is closed, the system may
399- // temporarily reserve that specific address+port pair in a so called
400- // TIME_WAIT state. During that period, any attempt to rebind to that pair
401- // will fail. Setting SO_REUSEADDR to true bypasses that behaviour. Unlike
402- // the name "SO_REUSEADDR" might suggest, it does not allow multiple
403- // active sockets to share the same local address.
404-
405- // On Windows that behavior is the default, so there is no need to manually
406- // configure such an option. But (!), Windows _does_ have an identically
407- // named socket option which allows users to "hijack" active sockets.
408- // This is definitely not what we want to do here.
409-
410- // Microsoft's own documentation[1] states that we should set SO_EXCLUSIVEADDRUSE
411- // instead (to the inverse value), however the github issue below[2] seems
412- // to indicate that that may no longer be correct.
413- // [1]: https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse
414- // [2]: https://github.com/python-trio/trio/issues/928
415-
416- #[ cfg( not( windows) ) ]
417- sockopt:: set_socket_reuseaddr ( sockfd, value) ?;
418- #[ cfg( windows) ]
419- let _ = ( sockfd, value) ;
420-
421- Ok ( ( ) )
422- }
423-
424- pub fn set_tcp_keepidle < Fd : AsFd > ( sockfd : Fd , value : Duration ) -> rustix:: io:: Result < ( ) > {
425- if value <= Duration :: ZERO {
426- // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
427- return Err ( Errno :: INVAL ) ;
428- }
429-
430- // Ensure that the value passed to the actual syscall never gets rounded down to 0.
431- const MIN_SECS : u64 = 1 ;
432-
433- // Cap it at Linux' maximum, which appears to have the lowest limit across our supported platforms.
434- const MAX_SECS : u64 = i16:: MAX as u64 ;
435-
436- sockopt:: set_tcp_keepidle (
437- sockfd,
438- value. clamp ( Duration :: from_secs ( MIN_SECS ) , Duration :: from_secs ( MAX_SECS ) ) ,
439- )
440- }
441-
442- pub fn set_tcp_keepintvl < Fd : AsFd > ( sockfd : Fd , value : Duration ) -> rustix:: io:: Result < ( ) > {
443- if value <= Duration :: ZERO {
444- // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
445- return Err ( Errno :: INVAL ) ;
446- }
447-
448- // Ensure that any fractional value passed to the actual syscall never gets rounded down to 0.
449- const MIN_SECS : u64 = 1 ;
450-
451- // Cap it at Linux' maximum, which appears to have the lowest limit across our supported platforms.
452- const MAX_SECS : u64 = i16:: MAX as u64 ;
453-
454- sockopt:: set_tcp_keepintvl (
455- sockfd,
456- value. clamp ( Duration :: from_secs ( MIN_SECS ) , Duration :: from_secs ( MAX_SECS ) ) ,
457- )
458- }
459-
460- pub fn set_tcp_keepcnt < Fd : AsFd > ( sockfd : Fd , value : u32 ) -> rustix:: io:: Result < ( ) > {
461- if value == 0 {
462- // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
463- return Err ( Errno :: INVAL ) ;
464- }
465-
466- const MIN_CNT : u32 = 1 ;
467- // Cap it at Linux' maximum, which appears to have the lowest limit across our supported platforms.
468- const MAX_CNT : u32 = i8:: MAX as u32 ;
469-
470- sockopt:: set_tcp_keepcnt ( sockfd, value. clamp ( MIN_CNT , MAX_CNT ) )
471- }
472-
473- pub fn get_ip_ttl < Fd : AsFd > ( sockfd : Fd ) -> rustix:: io:: Result < u8 > {
474- sockopt:: ip_ttl ( sockfd) ?
475- . try_into ( )
476- . map_err ( |_| Errno :: OPNOTSUPP )
477- }
478-
479- pub fn get_ipv6_unicast_hops < Fd : AsFd > ( sockfd : Fd ) -> rustix:: io:: Result < u8 > {
480- sockopt:: ipv6_unicast_hops ( sockfd)
481- }
482-
483- pub fn set_ip_ttl < Fd : AsFd > ( sockfd : Fd , value : u8 ) -> rustix:: io:: Result < ( ) > {
484- match value {
485- // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
486- //
487- // A well-behaved IP application should never send out new packets with TTL 0.
488- // We validate the value ourselves because OS'es are not consistent in this.
489- // On Linux the validation is even inconsistent between their IPv4 and IPv6 implementation.
490- 0 => Err ( Errno :: INVAL ) ,
491- _ => sockopt:: set_ip_ttl ( sockfd, value. into ( ) ) ,
492- }
493- }
494-
495- pub fn set_ipv6_unicast_hops < Fd : AsFd > ( sockfd : Fd , value : u8 ) -> rustix:: io:: Result < ( ) > {
496- match value {
497- 0 => Err ( Errno :: INVAL ) , // See `set_ip_ttl`
498- _ => sockopt:: set_ipv6_unicast_hops ( sockfd, Some ( value) ) ,
499- }
500- }
501-
502- fn normalize_get_buffer_size ( value : usize ) -> usize {
503- if cfg ! ( target_os = "linux" ) {
504- // Linux doubles the value passed to setsockopt to allow space for bookkeeping overhead.
505- // getsockopt returns this internally doubled value.
506- // We'll half the value to at least get it back into the same ballpark that the application requested it in.
507- //
508- // This normalized behavior is tested for in: test-programs/src/bin/preview2_tcp_sockopts.rs
509- value / 2
510- } else {
511- value
512- }
513- }
514-
515- fn normalize_set_buffer_size ( value : usize ) -> usize {
516- value. clamp ( 1 , i32:: MAX as usize )
517- }
518-
519- pub fn get_socket_recv_buffer_size < Fd : AsFd > ( sockfd : Fd ) -> rustix:: io:: Result < usize > {
520- let value = sockopt:: socket_recv_buffer_size ( sockfd) ?;
521- Ok ( normalize_get_buffer_size ( value) )
522- }
523-
524- pub fn get_socket_send_buffer_size < Fd : AsFd > ( sockfd : Fd ) -> rustix:: io:: Result < usize > {
525- let value = sockopt:: socket_send_buffer_size ( sockfd) ?;
526- Ok ( normalize_get_buffer_size ( value) )
527- }
528-
529- pub fn set_socket_recv_buffer_size < Fd : AsFd > (
530- sockfd : Fd ,
531- value : usize ,
532- ) -> rustix:: io:: Result < ( ) > {
533- if value == 0 {
534- // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
535- return Err ( Errno :: INVAL ) ;
536- }
537-
538- let value = normalize_set_buffer_size ( value) ;
539-
540- match sockopt:: set_socket_recv_buffer_size ( sockfd, value) {
541- // Most platforms (Linux, Windows, Fuchsia, Solaris, Illumos, Haiku, ESP-IDF, ..and more?) treat the value
542- // passed to SO_SNDBUF/SO_RCVBUF as a performance tuning hint and silently clamp the input if it exceeds
543- // their capability.
544- // As far as I can see, only the *BSD family views this option as a hard requirement and fails when the
545- // value is out of range. We normalize this behavior in favor of the more commonly understood
546- // "performance hint" semantics. In other words; even ENOBUFS is "Ok".
547- // A future improvement could be to query the corresponding sysctl on *BSD platforms and clamp the input
548- // `size` ourselves, to completely close the gap with other platforms.
549- //
550- // This normalized behavior is tested for in: test-programs/src/bin/preview2_tcp_sockopts.rs
551- Err ( Errno :: NOBUFS ) => Ok ( ( ) ) ,
552- r => r,
553- }
554- }
555-
556- pub fn set_socket_send_buffer_size < Fd : AsFd > (
557- sockfd : Fd ,
558- value : usize ,
559- ) -> rustix:: io:: Result < ( ) > {
560- if value == 0 {
561- // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
562- return Err ( Errno :: INVAL ) ;
563- }
564-
565- let value = normalize_set_buffer_size ( value) ;
566-
567- match sockopt:: set_socket_send_buffer_size ( sockfd, value) {
568- Err ( Errno :: NOBUFS ) => Ok ( ( ) ) , // See set_socket_recv_buffer_size
569- r => r,
570- }
571- }
572- }
0 commit comments