Skip to content

Commit 4de0130

Browse files
mayastor-borstiagolobocastro
andcommitted
Merge #1843
1843: Cherry-pick 1840 r=tiagolobocastro a=tiagolobocastro fix(tgt-iface): reject scope ids on ipv6 Start to add support for scope ids on ipv6. This is not yet supported on the nvmf target, I suspect because there's a bug on the socket interface which doesn't return the scope id as part of the traddr, though this is mostly a guess so far. As such, for the moment we detect but reject scope ids. --- fix(tgt-iface): support multiple ips per adapter In case adapter has multiple ipv6 ips, then the libc function getifaddrs returns multiple elements with the same name and so putting them on a map was actually erasing some ips! However, this now means we need to handle trying to pick one of the ips! For now we prioritize ipv4 vs 6 IF the grpc is also ipv4. Also we prefer ipv6 unique local, link local and !multicast. --- fix(tgt-iface): support ipv6 on tgt-iface The tgt-iface allows us a different way of specifying which interface, subnet or ip to bind the nvmf target to. This was also hardcoded to work with ipv4 mostly. --- fix(ipv6): support ipv6 for grpc and nvmf target In a few places ipv4 was still pretty much expected, leading into a few different crashes. Now we try to parse always as std::net::IpAddr which is an enum variant that can be either ipv4 or ipv6. Co-authored-by: Tiago Castro <[email protected]>
2 parents bcb7fbe + c2e7979 commit 4de0130

File tree

10 files changed

+341
-131
lines changed

10 files changed

+341
-131
lines changed

Cargo.lock

Lines changed: 5 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

io-engine/src/bin/io-engine-client/context.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@ impl Context {
135135
.unwrap_or('b');
136136
// Ensure the provided host is defaulted & normalized to what we expect.
137137
let host = if let Some(host) = matches.get_one::<String>("bind") {
138-
let uri = host.parse::<Uri>().context(InvalidUri)?;
138+
let uri = match host.parse::<Uri>().context(InvalidUri) {
139+
Ok(uri) => Ok(uri),
140+
Err(error) => format!("[{host}]").parse::<Uri>().map_err(|_| error),
141+
}?;
139142
let mut parts = uri.into_parts();
140143
if parts.scheme.is_none() {
141144
parts.scheme = Scheme::from_str("http").ok();

io-engine/src/bin/io-engine-client/v1/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub(super) async fn main_() -> crate::Result<()> {
2929
.short('b')
3030
.long("bind")
3131
.env("MY_POD_IP")
32-
.default_value("http://127.0.0.1:10124")
32+
.default_value("http://127.0.0.1")
3333
.value_name("HOST")
3434
.help("The URI of mayastor instance")
3535
.global(true),

io-engine/src/bin/io-engine.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ fn start_tokio_runtime(args: &MayastorCliArgs) {
152152
Registration::init(
153153
&node_name,
154154
&node_nqn,
155+
// todo: handle scope ids?
155156
&grpc_socket_addr.to_string(),
156157
registration_addr,
157158
api_versions,

io-engine/src/core/env.rs

Lines changed: 81 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::{
22
env,
33
ffi::CString,
4-
net::Ipv4Addr,
54
os::raw::{c_char, c_void},
65
pin::Pin,
76
str::FromStr,
@@ -39,6 +38,7 @@ use crate::{
3938
constants::NVME_NQN_PREFIX,
4039
core::{
4140
nic,
41+
nic::SIpAddr,
4242
reactor::{Reactor, ReactorState, Reactors},
4343
Cores, MayastorFeatures, Mthread,
4444
},
@@ -101,6 +101,30 @@ fn parse_crdt(src: &str) -> Result<[u16; TARGET_CRDT_LEN], String> {
101101
}
102102
}
103103

104+
/// Parses a grpc ip as a socket address.
105+
/// The port is ignored but we use the socket to keep the scope ip, which can be specified
106+
/// like so: `fe80::779c:c5ea:e9c5:2cc4%2`.
107+
/// # Warning
108+
/// Scope Ids are disabled for now as spdk posix sock seems to not format the traddr correctly
109+
/// when the endpoint has a scope id.
110+
fn parse_grpc_ip(src: &str) -> Result<SIpAddr, String> {
111+
let (ip, scope_ip) = match src.split_once('%') {
112+
Some(p) => p,
113+
None => (src, ""),
114+
};
115+
116+
match ip.parse::<std::net::IpAddr>() {
117+
Ok(ip) => match ip {
118+
std::net::IpAddr::V4(ip) => Ok(SIpAddr::from(ip)),
119+
std::net::IpAddr::V6(ip) if scope_ip.is_empty() => Ok(SIpAddr::from(ip)),
120+
std::net::IpAddr::V6(_) => Err(format!(
121+
"Ip '{src}' has a scope_id '{scope_ip}' which is not currently supported"
122+
)),
123+
},
124+
Err(error) => Err(format!("Invalid ip '{src}': {error}")),
125+
}
126+
}
127+
104128
#[derive(Debug, Clone, Parser)]
105129
#[clap(
106130
name = package_description!(),
@@ -112,9 +136,9 @@ pub struct MayastorCliArgs {
112136
#[deprecated = "Use grpc_ip and grpc_port instead"]
113137
/// IP address and port (optional) for the gRPC server to listen on.
114138
pub deprecated_grpc_endpoint: Option<String>,
115-
#[clap(long, default_value_t = std::net::IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED))]
139+
#[clap(long, value_parser = parse_grpc_ip, default_value_t = std::net::Ipv6Addr::UNSPECIFIED.into())]
116140
/// IP address for the gRPC server to listen on.
117-
pub grpc_ip: std::net::IpAddr,
141+
pub grpc_ip: SIpAddr,
118142
#[clap(long, default_value_t = 10124)]
119143
/// Port for the gRPC server to listen on.
120144
pub grpc_port: u16,
@@ -289,7 +313,7 @@ impl Default for MayastorCliArgs {
289313
#[allow(deprecated)]
290314
Self {
291315
deprecated_grpc_endpoint: None,
292-
grpc_ip: std::net::IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED),
316+
grpc_ip: std::net::Ipv6Addr::UNSPECIFIED.into(),
293317
grpc_port: 10124,
294318
ps_endpoint: None,
295319
ps_timeout: Duration::from_secs(10),
@@ -340,7 +364,8 @@ impl MayastorCliArgs {
340364
if let Some(deprecated_endpoint) = &self.deprecated_grpc_endpoint {
341365
grpc::endpoint_from_str(deprecated_endpoint, self.grpc_port)
342366
} else {
343-
std::net::SocketAddr::new(self.grpc_ip, self.grpc_port)
367+
// todo: scope ids are not properly supported on SPDK
368+
std::net::SocketAddr::new(self.grpc_ip.ip(), self.grpc_port)
344369
}
345370
}
346371
}
@@ -790,8 +815,8 @@ impl MayastorEnvironment {
790815
}
791816

792817
/// Returns NVMF target's IP address.
793-
pub(crate) fn get_nvmf_tgt_ip() -> Result<String, String> {
794-
static TGT_IP: OnceCell<String> = OnceCell::new();
818+
pub(crate) fn get_nvmf_tgt_ip() -> Result<SIpAddr, String> {
819+
static TGT_IP: OnceCell<SIpAddr> = OnceCell::new();
795820
TGT_IP
796821
.get_or_try_init(|| match Self::global_or_default().nvmf_tgt_interface {
797822
Some(ref iface) => Self::detect_nvmf_tgt_iface_ip(iface),
@@ -807,86 +832,90 @@ impl MayastorEnvironment {
807832

808833
/// Detects IP address for NVMF target by the interface specified in CLI
809834
/// arguments.
810-
fn detect_nvmf_tgt_iface_ip(iface: &str) -> Result<String, String> {
811-
info!(
812-
"Detecting IP address for NVMF target network interface \
813-
specified as '{}' ...",
814-
iface
815-
);
835+
fn detect_nvmf_tgt_iface_ip(iface: &str) -> Result<SIpAddr, String> {
836+
// In case of matching by name, mac or subnet, the adapter may have multiple ips, and
837+
// in this case how to prioritize them?
838+
// For now, we can at least match ipv4 or ipv6 to match the grpc.
839+
let env = MayastorEnvironment::global_or_default();
840+
let prefer_ipv4 = Self::prefer_ipv4(env.grpc_endpoint.expect("Should always be set"));
841+
info!("Detecting IP address (prefer ipv4: {prefer_ipv4}) for NVMF target network interface specified as '{iface}' ...");
816842

817843
let (cls, name) = match iface.split_once(':') {
818844
Some(p) => p,
819845
None => ("name", iface),
820846
};
821847

822-
let pred: Box<dyn Fn(&nic::Interface) -> bool> = match cls {
823-
"name" => Box::new(|n| n.name == name),
848+
let map_ok = |filter: bool, n: nic::Interface| -> Option<nic::Interface> {
849+
if filter {
850+
Some(n)
851+
} else {
852+
None
853+
}
854+
};
855+
856+
let pred: Box<dyn FnMut(nic::Interface) -> Option<nic::Interface>> = match cls {
857+
"name" => Box::new(|n| map_ok(n.name == name, n)),
824858
"mac" => {
825859
let mac = Some(name.parse::<nic::MacAddr>()?);
826-
Box::new(move |n| n.mac == mac)
860+
Box::new(move |n| map_ok(n.mac == mac, n))
827861
}
828862
"ip" => {
829-
let addr = Some(nic::parse_ipv4(name)?);
830-
Box::new(move |n| n.inet.addr == addr)
863+
let addr = nic::parse_ip(name)?;
864+
Box::new(move |n| n.matched_ip_kind(addr))
831865
}
832866
"subnet" => {
833-
let (subnet, mask) = nic::parse_ipv4_subnet(name)?;
834-
Box::new(move |n| n.ipv4_subnet_eq(subnet, mask))
867+
let (subnet, mask) = nic::parse_ip_subnet(name)?;
868+
Box::new(move |n| n.matched_subnet_kind(subnet, mask))
835869
}
836870
_ => {
837-
return Err(format!("Invalid NVMF target interface: '{iface}'",));
871+
return Err(format!("Invalid NVMF target interface: '{iface}'"));
838872
}
839873
};
840874

841-
let mut nics: Vec<_> = nic::find_all_nics().into_iter().filter(pred).collect();
875+
let mut nics: Vec<_> = nic::find_all_nics().into_iter().filter_map(pred).collect();
876+
nics.sort_by(|a, b| a.sort(b, prefer_ipv4));
842877

843-
if nics.is_empty() {
844-
return Err(format!("Network interface matching '{iface}' not found",));
845-
}
846-
847-
if nics.len() > 1 {
848-
return Err(format!(
849-
"Multiple network interfaces that \
850-
match '{iface}' are found",
851-
));
852-
}
878+
let res: nic::Interface = match nics.pop() {
879+
None => Err(format!("Network interface matching '{iface}' not found")),
880+
Some(nic) => Ok(nic),
881+
}?;
853882

854-
let res = nics.pop().unwrap();
855-
856-
info!(
857-
"NVMF target network interface '{}' matches to {}",
858-
iface, res
859-
);
883+
info!("NVMF target network interface '{iface}' matches to {res}");
860884

861-
if res.inet.addr.is_none() {
862-
return Err(format!(
863-
"Network interface '{}' has no IPv4 address configured",
885+
match res.ip(prefer_ipv4) {
886+
Some(ip) => Ok(ip),
887+
None => Err(format!(
888+
"Network interface '{}' has no IP address configured",
864889
res.name
865-
));
890+
)),
866891
}
892+
}
867893

868-
Ok(res.inet.addr.unwrap().to_string())
894+
fn prefer_ipv4(grpc: std::net::SocketAddr) -> bool {
895+
match grpc {
896+
std::net::SocketAddr::V4(_) => true,
897+
std::net::SocketAddr::V6(socket) if socket.ip().is_unspecified() => true,
898+
std::net::SocketAddr::V6(_) => false,
899+
}
869900
}
870901

871902
/// Detects pod IP address.
872-
fn detect_pod_ip() -> Result<String, String> {
903+
fn detect_pod_ip() -> Result<SIpAddr, String> {
873904
match env::var("MY_POD_IP") {
874905
Ok(val) => {
875906
info!(
876907
"Using 'MY_POD_IP' environment variable for IP address \
877908
for NVMF target network interface"
878909
);
879-
880-
if val.parse::<Ipv4Addr>().is_ok() {
881-
Ok(val)
910+
Ok(parse_grpc_ip(&val)?)
911+
}
912+
Err(_) => {
913+
if Self::prefer_ipv4(Self::global_or_default().grpc_endpoint.unwrap()) {
914+
Ok(std::net::Ipv4Addr::LOCALHOST.into())
882915
} else {
883-
Err(format!(
884-
"MY_POD_IP environment variable is set to an \
885-
invalid IPv4 address: '{val}'"
886-
))
916+
Ok(std::net::Ipv6Addr::LOCALHOST.into())
887917
}
888918
}
889-
Err(_) => Ok("127.0.0.1".to_owned()),
890919
}
891920
}
892921

io-engine/src/core/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub use env::{mayastor_env_stop, MayastorCliArgs, MayastorEnvironment, GLOBAL_RC
2424
pub use handle::{BdevHandle, UntypedBdevHandle};
2525
pub use io_device::IoDevice;
2626
pub use logical_volume::LogicalVolume;
27+
pub use nic::SIpAddr;
2728
pub use reactor::{reactor_monitor_loop, Reactor, ReactorState, Reactors, REACTOR_LIST};
2829

2930
pub use lock::{

0 commit comments

Comments
 (0)