Skip to content

Commit ad9585e

Browse files
authored
Fall back to ipv4 if dual stack sockets are disabled (#26)
fix: Fall back to ipv4 if dual stack sockets are disabled
1 parent a2050d1 commit ad9585e

File tree

5 files changed

+69
-29
lines changed

5 files changed

+69
-29
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mgmtd/src/grpc.rs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use sqlite::{TransactionExt, check_affected_rows};
1616
use sqlite_check::sql;
1717
use std::fmt::Debug;
1818
use std::future::Future;
19-
use std::net::{SocketAddr, TcpListener};
19+
use std::net::SocketAddr;
2020
use std::pin::Pin;
2121
use tonic::transport::{Identity, Server, ServerTlsConfig};
2222
use tonic::{Code, Request, Response, Status};
@@ -210,19 +210,7 @@ pub(crate) fn serve(ctx: Context, mut shutdown: RunStateHandle) -> Result<()> {
210210
},
211211
);
212212

213-
let mut serve_addr = SocketAddr::new("::".parse()?, ctx.info.user_config.grpc_port);
214-
215-
// Test for IPv6 available, fall back to IPv4 sockets if not
216-
match TcpListener::bind(serve_addr) {
217-
Ok(_) => {}
218-
Err(err) if err.raw_os_error() == Some(libc::EAFNOSUPPORT) => {
219-
log::debug!("gRPC: IPv6 not available, falling back to IPv4 sockets");
220-
serve_addr = SocketAddr::new("0.0.0.0".parse()?, ctx.info.user_config.grpc_port);
221-
}
222-
Err(err) => {
223-
anyhow::bail!(err);
224-
}
225-
}
213+
let serve_addr = shared::nic::select_bind_addr(ctx.info.user_config.grpc_port);
226214

227215
log::info!("Serving gRPC requests on {serve_addr}");
228216

mgmtd/src/lib.rs

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ use sqlite::TransactionExt;
2727
use sqlite_check::sql;
2828
use std::collections::HashSet;
2929
use std::future::Future;
30-
use std::net::{SocketAddr, TcpListener};
3130
use std::sync::Arc;
3231
use tokio::net::UdpSocket;
3332
use tokio::sync::mpsc;
@@ -60,19 +59,7 @@ pub async fn start(info: StaticInfo, license: LicenseVerifier) -> Result<RunCont
6059
// Static configuration which doesn't change at runtime
6160
let info = Box::leak(Box::new(info));
6261

63-
let mut beemsg_serve_addr = SocketAddr::new("::".parse()?, info.user_config.beemsg_port);
64-
65-
// Test for IPv6 available, fall back to IPv4 sockets if not
66-
match TcpListener::bind(beemsg_serve_addr) {
67-
Ok(_) => {}
68-
Err(err) if err.raw_os_error() == Some(libc::EAFNOSUPPORT) => {
69-
log::debug!("BeeMsg: IPv6 not available, falling back to IPv4 sockets");
70-
beemsg_serve_addr = SocketAddr::new("0.0.0.0".parse()?, info.user_config.beemsg_port);
71-
}
72-
Err(err) => {
73-
anyhow::bail!(err);
74-
}
75-
}
62+
let beemsg_serve_addr = shared::nic::select_bind_addr(info.user_config.beemsg_port);
7663

7764
// UDP socket for in- and outgoing messages
7865
let udp_socket = Arc::new(UdpSocket::bind(beemsg_serve_addr).await?);

shared/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ bee_serde_derive = { path = "../bee_serde_derive" }
1212

1313
anyhow = { workspace = true }
1414
bytes = { workspace = true }
15+
libc = { workspace = true }
1516
log = { workspace = true }
1617
pnet_datalink = "0"
1718
protobuf = { workspace = true, optional = true }

shared/src/nic.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use crate::types::NicType;
22
use anyhow::{Result, anyhow};
33
use serde::Deserializer;
44
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};
67
use 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(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+
210273
#[cfg(test)]
211274
mod test {
212275
use super::*;

0 commit comments

Comments
 (0)