Skip to content

Commit

Permalink
Prevent modunload once underlay has been set
Browse files Browse the repository at this point in the history
This PR moves the underlay teardown logic from xde_detach into a new
ioctl (and opteadm command) to unset the XDE underlay. Detach will now
fail so long as the underlay is set -- this successfully prevents
`modunload` from knocking us down after this state has been set.

The semantics are more or less what you'd expect:
* Clearing the underlay will fail if there exists a port.
* Clearing the underlay will fail if there is no underlay.
* Driver detach now requires that you `opteadm clear-xde-underlay`.
  • Loading branch information
FelixMcFelix committed Apr 5, 2024
1 parent f9220e7 commit 03d371b
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 49 deletions.
7 changes: 7 additions & 0 deletions bin/opteadm/src/bin/opteadm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ enum Command {
/// Set up xde underlay devices
SetXdeUnderlay { u1: String, u2: String },

/// Clear xde underlay devices
ClearXdeUnderlay,

/// Set a virtual-to-physical mapping
SetV2P { vpc_ip: IpAddr, vpc_mac: MacAddr, underlay_ip: Ipv6Addr, vni: Vni },

Expand Down Expand Up @@ -645,6 +648,10 @@ fn main() -> anyhow::Result<()> {
let _ = hdl.set_xde_underlay(&u1, &u2)?;
}

Command::ClearXdeUnderlay => {
let _ = hdl.clear_xde_underlay()?;
}

Command::RmFwRule { port, direction, id } => {
let request = RemFwRuleReq { port_name: port, dir: direction, id };
hdl.remove_firewall_rule(&request)?;
Expand Down
8 changes: 8 additions & 0 deletions bin/opteadm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

//! OPTE driver administration library

use opte::api::ClearXdeUnderlayReq;
use opte::api::NoResp;
use opte::api::OpteCmd;
use opte::api::SetXdeUnderlayReq;
Expand Down Expand Up @@ -92,6 +93,13 @@ impl OpteAdm {
run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req))
}

/// Clear xde underlay devices
pub fn clear_xde_underlay(&self) -> Result<NoResp, Error> {
let req = ClearXdeUnderlayReq { _unused: 0 };
let cmd = OpteCmd::ClearXdeUnderlay;
run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req))
}

/// Add a firewall rule
pub fn add_firewall_rule(
&self,
Expand Down
2 changes: 2 additions & 0 deletions crates/opte-api/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub enum OpteCmd {
CreateXde = 70, // create a new xde device
DeleteXde = 71, // delete an xde device
SetXdeUnderlay = 72, // set xde underlay devices
ClearXdeUnderlay = 73, // clear xde underlay devices
SetExternalIps = 80, // set xde external IPs for a port
}

Expand Down Expand Up @@ -66,6 +67,7 @@ impl TryFrom<c_int> for OpteCmd {
70 => Ok(Self::CreateXde),
71 => Ok(Self::DeleteXde),
72 => Ok(Self::SetXdeUnderlay),
73 => Ok(Self::ClearXdeUnderlay),
80 => Ok(Self::SetExternalIps),
_ => Err(()),
}
Expand Down
8 changes: 7 additions & 1 deletion crates/opte-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub use ulp::*;
///
/// We rely on CI and the check-api-version.sh script to verify that
/// this number is incremented anytime the oxide-api code changes.
pub const API_VERSION: u64 = 28;
pub const API_VERSION: u64 = 29;

/// Major version of the OPTE package.
pub const MAJOR_VERSION: u64 = 0;
Expand Down Expand Up @@ -87,3 +87,9 @@ pub struct SetXdeUnderlayReq {
pub u1: String,
pub u2: String,
}

/// Clear the underlay devices used by the xde kernel module
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ClearXdeUnderlayReq {
pub _unused: u64,
}
8 changes: 8 additions & 0 deletions lib/opte-ioctl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

// Copyright 2022 Oxide Computer Company

use opte::api::ClearXdeUnderlayReq;
use opte::api::CmdOk;
use opte::api::NoResp;
use opte::api::OpteCmd;
Expand Down Expand Up @@ -171,6 +172,13 @@ impl OpteHdl {
run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req))
}

/// Clear xde underlay devices.
pub fn clear_xde_underlay(&self) -> Result<NoResp, Error> {
let req = ClearXdeUnderlayReq { _unused: 0 };
let cmd = OpteCmd::ClearXdeUnderlay;
run_cmd_ioctl(self.device.as_raw_fd(), cmd, Some(&req))
}

pub fn add_router_entry(
&self,
req: &AddRouterEntryReq,
Expand Down
150 changes: 102 additions & 48 deletions xde/src/xde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ use core::num::NonZeroU32;
use core::ptr;
use core::time::Duration;
use illumos_sys_hdrs::*;
use opte::api::ClearXdeUnderlayReq;
use opte::api::CmdOk;
use opte::api::Direction;
use opte::api::NoResp;
Expand Down Expand Up @@ -258,7 +259,7 @@ fn get_xde_state() -> &'static XdeState {
// it was derived from Box::into_raw() during `xde_attach`.
unsafe {
let p = ddi_get_driver_private(xde_dip);
&mut *(p as *mut XdeState)
&*(p as *mut XdeState)
}
}

Expand Down Expand Up @@ -474,6 +475,13 @@ fn set_xde_underlay_hdlr(env: &mut IoctlEnvelope) -> Result<NoResp, OpteError> {
set_xde_underlay(&req)
}

fn clear_xde_underlay_hdlr(
env: &mut IoctlEnvelope,
) -> Result<NoResp, OpteError> {
let _req: ClearXdeUnderlayReq = env.copy_in_req()?;
clear_xde_underlay()
}

// This is the entry point for all OPTE commands. It verifies the API
// version and then multiplexes the command to its appropriate handler.
#[no_mangle]
Expand Down Expand Up @@ -527,6 +535,11 @@ unsafe extern "C" fn xde_ioc_opte_cmd(karg: *mut c_void, mode: c_int) -> c_int {
hdlr_resp(&mut env, resp)
}

OpteCmd::ClearXdeUnderlay => {
let resp = clear_xde_underlay_hdlr(&mut env);
hdlr_resp(&mut env, resp)
}

OpteCmd::DumpLayer => {
let resp = dump_layer_hdlr(&mut env);
hdlr_resp(&mut env, resp)
Expand Down Expand Up @@ -856,6 +869,84 @@ fn set_xde_underlay(req: &SetXdeUnderlayReq) -> Result<NoResp, OpteError> {
Ok(NoResp::default())
}

#[no_mangle]
fn clear_xde_underlay() -> Result<NoResp, OpteError> {
let state = get_xde_state();
let mut underlay = state.underlay.lock();
if underlay.is_none() {
return Err(OpteError::System {
errno: ENOENT,
msg: "underlay not yet initialized".into(),
});
}
if unsafe { xde_devs.read().len() } > 0 {
return Err(OpteError::System {
errno: EBUSY,
msg: "underlay in use by attached ports".into(),
});
}

if let Some(underlay) = underlay.take() {
// There shouldn't be anymore refs to the underlay given we checked for
// 0 ports above.
let Ok(u1) = Arc::try_unwrap(underlay.u1) else {
return Err(OpteError::System {
errno: EBUSY,
msg: "underlay u1 has outstanding refs".into(),
});
};
let Ok(u2) = Arc::try_unwrap(underlay.u2) else {
return Err(OpteError::System {
errno: EBUSY,
msg: "underlay u2 has outstanding refs".into(),
});
};

for u in [u1, u2] {
// Clear all Rx paths
u.mch.clear_rx();

// We have a chain of refs here:
// 1. `MacPromiscHandle` holds a ref to `MacClientHandle`, and
// 2. `MacUnicastHandle` holds a ref to `MacClientHandle`, and
// 3. `MacClientHandle` holds a ref to `MacHandle`.
// We explicitly drop them in order here to ensure there are no
// outstanding refs.

// 1. Remove promisc and unicast callbacks
drop(u.mph);
drop(u.muh);

// 2. Remove MAC client handle
if Arc::strong_count(&u.mch) > 1 {
warn!(
"underlay {} has outstanding mac client handle refs",
u.name
);
return Err(OpteError::System {
errno: EBUSY,
msg: format!("underlay {} has outstanding refs", u.name),
});
}
drop(u.mch);

// Finally, we can cleanup the MAC handle for this underlay
if Arc::strong_count(&u.mh) > 1 {
return Err(OpteError::System {
errno: EBUSY,
msg: format!(
"underlay {} has outstanding mac handle refs",
u.name
),
});
}
drop(u.mh);
}
}

Ok(NoResp::default())
}

const IOCTL_SZ: usize = core::mem::size_of::<OpteCmdIoctl>();

#[no_mangle]
Expand Down Expand Up @@ -1129,57 +1220,20 @@ unsafe extern "C" fn xde_detach(
return DDI_FAILURE;
}

let rstate = ddi_get_driver_private(xde_dip);
assert!(!rstate.is_null());
let state = Box::from_raw(rstate as *mut XdeState);
let underlay = state.underlay.into_inner();

if let Some(underlay) = underlay {
// There shouldn't be anymore refs to the underlay given we checked for
// 0 ports above.
let Ok(u1) = Arc::try_unwrap(underlay.u1) else {
warn!("failed to detach: underlay u1 has outstanding refs");
return DDI_FAILURE;
};
let Ok(u2) = Arc::try_unwrap(underlay.u2) else {
warn!("failed to detach: underlay u2 has outstanding refs");
return DDI_FAILURE;
};

for u in [u1, u2] {
// Clear all Rx paths
u.mch.clear_rx();

// We have a chain of refs here:
// 1. `MacPromiscHandle` holds a ref to `MacClientHandle`, and
// 2. `MacUnicastHandle` holds a ref to `MacClientHandle`, and
// 3. `MacClientHandle` holds a ref to `MacHandle`.
// We explicitly drop them in order here to ensure there are no
// outstanding refs.

// 1. Remove promisc and unicast callbacks
drop(u.mph);
drop(u.muh);
let state = ddi_get_driver_private(xde_dip) as *mut XdeState;
assert!(!state.is_null());

// 2. Remove MAC client handle
if Arc::strong_count(&u.mch) > 1 {
warn!(
"underlay {} has outstanding mac client handle refs",
u.name
);
return DDI_FAILURE;
}
drop(u.mch);
let state_ref = &*(state);
let underlay = state_ref.underlay.lock();

// Finally, we can cleanup the MAC handle for this underlay
if Arc::strong_count(&u.mh) > 1 {
warn!("underlay {} has outstanding mac handle refs", u.name);
return DDI_FAILURE;
}
drop(u.mh);
}
if underlay.is_some() {
warn!("failed to detach: underlay is set");
return DDI_FAILURE;
}

// Reattach the XdeState to a Box, which will free it on drop.
drop(Box::from_raw(state));

// Remove control device
ddi_remove_minor_node(xde_dip, XDE_STR);

Expand Down

0 comments on commit 03d371b

Please sign in to comment.