Skip to content

Commit 9ed8e8d

Browse files
committed
fix: Fall back to ipv4 if dual stack sockets are disabled
1 parent a2050d1 commit 9ed8e8d

File tree

5 files changed

+76
-29
lines changed

5 files changed

+76
-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: 5 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,10 @@ 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 = SocketAddr::new(
214+
shared::nic::select_bind_addr(),
215+
ctx.info.user_config.grpc_port,
216+
);
226217

227218
log::info!("Serving gRPC requests on {serve_addr}");
228219

mgmtd/src/lib.rs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use sqlite::TransactionExt;
2727
use sqlite_check::sql;
2828
use std::collections::HashSet;
2929
use std::future::Future;
30-
use std::net::{SocketAddr, TcpListener};
30+
use std::net::SocketAddr;
3131
use std::sync::Arc;
3232
use tokio::net::UdpSocket;
3333
use tokio::sync::mpsc;
@@ -60,19 +60,10 @@ pub async fn start(info: StaticInfo, license: LicenseVerifier) -> Result<RunCont
6060
// Static configuration which doesn't change at runtime
6161
let info = Box::leak(Box::new(info));
6262

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-
}
63+
let beemsg_serve_addr = SocketAddr::new(
64+
shared::nic::select_bind_addr(),
65+
info.user_config.beemsg_port,
66+
);
7667

7768
// UDP socket for in- and outgoing messages
7869
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};
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() -> 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)]
211274
mod test {
212275
use super::*;

0 commit comments

Comments
 (0)