@@ -2,7 +2,8 @@ use crate::types::NicType;
22use anyhow:: { Result , anyhow} ;
33use serde:: Deserializer ;
44use serde:: de:: { Unexpected , Visitor } ;
5- use std:: net:: IpAddr ;
5+ use std:: net:: { IpAddr , Ipv4Addr , Ipv6Addr } ;
6+ use std:: os:: fd:: { AsRawFd , FromRawFd , OwnedFd } ;
67use std:: str:: FromStr ;
78
89/// Network protocol
@@ -207,6 +208,68 @@ pub fn query_nics(filter: &[NicFilter]) -> Result<Vec<Nic>> {
207208 Ok ( filtered_nics)
208209}
209210
211+ /// Selects address to bind to for listening: Checks if IPv6 sockets are available on this host
212+ /// according to our rules: IPv6 must be enabled during boot and at runtime, and IPv6 sockets must
213+ /// be dual stack. Then it returns `::` (IPv6), otherwise `0.0.0.0` (IPv4).
214+ pub fn select_bind_addr ( ) -> IpAddr {
215+ // SAFETY: Any data used in the libc calls is local only
216+ unsafe {
217+ // Check if IPv6 socket can be created
218+ let sock = libc:: socket ( libc:: AF_INET6 , libc:: SOCK_STREAM , 0 ) ;
219+ if sock < 0 {
220+ log:: info!( "IPv6 is unavailable on this host, falling back to IPv4 sockets" ) ;
221+ return Ipv4Addr :: UNSPECIFIED . into ( ) ;
222+ }
223+ // Make sure the socket is closed on drop
224+ let sock = OwnedFd :: from_raw_fd ( sock) ;
225+
226+ // Check if we can connect the socket to ipv6. We are not interested in an actual connection
227+ // here but rather if it fails with EADDRNOTAVAIL, which indicates ipv6 is loaded in
228+ // kernel but disabled at runtime
229+ libc:: fcntl ( sock. as_raw_fd ( ) , libc:: F_SETFL , libc:: O_NONBLOCK ) ;
230+ let addr_in6 = libc:: sockaddr_in6 {
231+ sin6_family : libc:: AF_INET6 as u16 ,
232+ sin6_port : 35037 , // Just a random port
233+ sin6_flowinfo : 0 ,
234+ sin6_addr : libc:: in6_addr {
235+ s6_addr : Ipv6Addr :: LOCALHOST . octets ( ) ,
236+ } ,
237+ sin6_scope_id : 0 ,
238+ } ;
239+ let res = libc:: connect (
240+ sock. as_raw_fd ( ) ,
241+ & addr_in6 as * const _ as * const _ ,
242+ size_of :: < libc:: sockaddr_in6 > ( ) as u32 ,
243+ ) ;
244+
245+ if res < 0 && std:: io:: Error :: last_os_error ( ) . raw_os_error ( ) == Some ( libc:: EADDRNOTAVAIL ) {
246+ log:: info!( "IPv6 is disabled on this host, falling back to IPv4 sockets" ) ;
247+ return Ipv4Addr :: UNSPECIFIED . into ( ) ;
248+ }
249+
250+ // Check if dual stack sockets are enabled by querying the socket option
251+ let mut ipv6_only: std:: ffi:: c_int = 0 ;
252+ let mut ipv6_only_size = size_of :: < std:: ffi:: c_int > ( ) ;
253+
254+ let res = libc:: getsockopt (
255+ sock. as_raw_fd ( ) ,
256+ libc:: IPPROTO_IPV6 ,
257+ libc:: IPV6_V6ONLY ,
258+ & mut ipv6_only as * mut _ as * mut _ ,
259+ & mut ipv6_only_size as * mut _ as * mut _ ,
260+ ) ;
261+
262+ if res < 0 || ipv6_only == 1 {
263+ log:: info!(
264+ "IPv6 dual stack sockets are unavailable on this host, falling back to IPv4 sockets"
265+ ) ;
266+ return Ipv4Addr :: UNSPECIFIED . into ( ) ;
267+ }
268+ }
269+
270+ Ipv6Addr :: UNSPECIFIED . into ( )
271+ }
272+
210273#[ cfg( test) ]
211274mod test {
212275 use super :: * ;
0 commit comments