@@ -2,7 +2,8 @@ use crate::types::NicType;
2
2
use anyhow:: { Result , anyhow} ;
3
3
use serde:: Deserializer ;
4
4
use serde:: de:: { Unexpected , Visitor } ;
5
- use std:: net:: IpAddr ;
5
+ use std:: net:: { IpAddr , Ipv4Addr , Ipv6Addr , SocketAddr } ;
6
+ use std:: os:: fd:: { AsRawFd , FromRawFd , OwnedFd } ;
6
7
use std:: str:: FromStr ;
7
8
8
9
/// Network protocol
@@ -207,6 +208,68 @@ pub fn query_nics(filter: &[NicFilter]) -> Result<Vec<Nic>> {
207
208
Ok ( filtered_nics)
208
209
}
209
210
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 ( port : u16 ) -> SocketAddr {
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 SocketAddr :: new ( Ipv4Addr :: UNSPECIFIED . into ( ) , port) ;
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 : libc:: htons ( 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 SocketAddr :: new ( Ipv4Addr :: UNSPECIFIED . into ( ) , port) ;
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 libc:: c_void ,
259
+ & mut ipv6_only_size as * mut _ as * mut libc:: socklen_t ,
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 SocketAddr :: new ( Ipv4Addr :: UNSPECIFIED . into ( ) , port) ;
267
+ }
268
+ }
269
+
270
+ SocketAddr :: new ( Ipv6Addr :: UNSPECIFIED . into ( ) , port)
271
+ }
272
+
210
273
#[ cfg( test) ]
211
274
mod test {
212
275
use super :: * ;
0 commit comments