use std::net::SocketAddr;
use std::ops::{Deref, DerefMut};
use std::sync::LazyLock;
use std::sync::atomic::AtomicU16;
use nfs3_types::mount::{dirpath, mountres3_ok};
use nfs3_types::nfs3::nfs_fh3;
use nfs3_types::rpc::opaque_auth;
use nfs3_types::xdr_codec::Opaque;
use crate::io::{AsyncRead, AsyncWrite};
use crate::{ConnectError, MountClient, Nfs3Client, RpcError, mount, portmapper};
#[derive(Debug)]
pub struct Nfs3Connection<IO> {
pub host: String,
pub mount_port: u16,
pub mount_path: dirpath<'static>,
pub mount_client: MountClient<IO>,
pub mount_resok: mountres3_ok<'static>,
pub nfs3_port: u16,
pub nfs3_client: Nfs3Client<IO>,
}
impl<IO> Nfs3Connection<IO>
where
IO: AsyncRead + AsyncWrite + Send,
{
pub fn root_nfs_fh3(&self) -> nfs_fh3 {
nfs_fh3 {
data: self.mount_resok.fhandle.0.clone(),
}
}
pub async fn unmount(mut self) -> Result<(), RpcError> {
self.mount_client.umnt(self.mount_path).await
}
pub fn into_nfs3_client(self) -> Nfs3Client<IO> {
self.nfs3_client
}
}
impl<IO> Deref for Nfs3Connection<IO> {
type Target = Nfs3Client<IO>;
fn deref(&self) -> &Self::Target {
&self.nfs3_client
}
}
impl<IO> DerefMut for Nfs3Connection<IO> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.nfs3_client
}
}
pub struct Nfs3ConnectionBuilder<C> {
host: String,
connector: C,
connect_from_privileged_port: bool,
portmapper_port: u16,
mount_port: Option<u16>,
nfs3_port: Option<u16>,
mount_path: dirpath<'static>,
credential: opaque_auth<'static>,
verifier: opaque_auth<'static>,
}
impl<C, S> Nfs3ConnectionBuilder<C>
where
C: crate::net::Connector<Connection = S>,
S: AsyncRead + AsyncWrite + Send,
{
pub fn new(connector: C, host: impl AsRef<str>, mount_path: impl AsRef<str>) -> Self {
Self {
host: host.as_ref().into(),
connector,
connect_from_privileged_port: true,
portmapper_port: nfs3_types::portmap::PMAP_PORT,
mount_port: None,
nfs3_port: None,
mount_path: dirpath(Opaque::owned(mount_path.as_ref().as_bytes().to_vec())),
credential: opaque_auth::default(),
verifier: opaque_auth::default(),
}
}
#[must_use]
pub const fn connect_from_privileged_port(mut self, connect: bool) -> Self {
self.connect_from_privileged_port = connect;
self
}
#[must_use]
pub const fn portmapper_port(mut self, port: u16) -> Self {
self.portmapper_port = port;
self
}
#[must_use]
pub const fn mount_port(mut self, port: u16) -> Self {
self.mount_port = Some(port);
self
}
#[must_use]
pub const fn nfs3_port(mut self, port: u16) -> Self {
self.nfs3_port = Some(port);
self
}
#[must_use]
pub fn credential(mut self, credential: opaque_auth<'static>) -> Self {
self.credential = credential;
self
}
#[must_use]
pub fn verifier(mut self, verifier: opaque_auth<'static>) -> Self {
self.verifier = verifier;
self
}
pub async fn mount(self) -> Result<Nfs3Connection<S>, ConnectError> {
let (mount_port, nfs3_port) = self.resolve_ports().await?;
let io = self.connect(mount_port).await?;
let mut mount_client =
mount::MountClient::new_with_auth(io, self.credential.clone(), self.verifier.clone());
let borrowed_mount_path = dirpath(Opaque::borrowed(self.mount_path.0.as_ref()));
let mount_resok = mount_client.mnt(borrowed_mount_path).await?;
let io = self.connect(nfs3_port).await?;
let nfs3_client = Nfs3Client::new_with_auth(io, self.credential, self.verifier);
Ok(Nfs3Connection {
host: self.host,
mount_port,
mount_path: self.mount_path,
mount_client,
mount_resok,
nfs3_port,
nfs3_client,
})
}
async fn resolve_ports(&self) -> Result<(u16, u16), ConnectError> {
if let (Some(mount_port), Some(nfs3_port)) = (self.mount_port, self.nfs3_port) {
return Ok((mount_port, nfs3_port));
}
let addr = self
.host
.parse()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
let io = self
.connector
.connect(SocketAddr::new(addr, self.portmapper_port))
.await?;
let mut portmapper = portmapper::PortmapperClient::new(io);
let mount_port = if let Some(port) = self.mount_port {
port
} else {
portmapper
.getport(
nfs3_types::mount::PROGRAM,
nfs3_types::mount::VERSION,
nfs3_types::portmap::IPPROTO_TCP,
)
.await?
};
let nfs3_port = if let Some(port) = self.nfs3_port {
port
} else {
portmapper
.getport(
nfs3_types::nfs3::PROGRAM,
nfs3_types::nfs3::VERSION,
nfs3_types::portmap::IPPROTO_TCP,
)
.await?
};
Ok((mount_port, nfs3_port))
}
async fn connect(&self, port: u16) -> std::io::Result<S> {
let addr = self
.host
.parse()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
let socket_addr = SocketAddr::new(addr, port);
if self.connect_from_privileged_port {
connect_from_privileged_port(&self.connector, socket_addr).await
} else {
self.connector.connect(socket_addr).await
}
}
}
async fn connect_from_privileged_port<C, S>(connector: &C, addr: SocketAddr) -> std::io::Result<S>
where
C: crate::net::Connector<Connection = S>,
S: AsyncRead + AsyncWrite + Send,
{
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
const MIN_PORT: u16 = 300;
const MAX_PORT: u16 = 1023;
static PORT_INDEX: LazyLock<AtomicU16> = LazyLock::new(|| AtomicU16::new(fastrand::u16(..)));
for _ in MIN_PORT..=MAX_PORT {
let index = PORT_INDEX.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let local_port = MIN_PORT + (index % (MAX_PORT - MIN_PORT));
let result = connector.connect_with_port(addr, local_port).await;
match &result {
Err(e) if e.kind() == IoErrorKind::AddrInUse => {
}
Ok(_) | Err(_) => {
return result;
}
}
}
Err(IoError::other(format!(
"Failed to connect to mount service from privileged port range ({MIN_PORT}-{MAX_PORT})"
)))
}