use std::mem::zeroed;
use crate::landlock::{
compat::private::OptionCompatLevelMut, uapi, Access, AddRuleError, AddRulesError, CompatError,
CompatLevel, CompatResult, CompatState, Compatible, HandleAccessError, HandleAccessesError,
PrivateAccess, PrivateRule, Rule, Ruleset, RulesetCreated, TailoredCompatLevel, TryCompat, ABI,
};
crate::landlock::access::bitflags_type! {
pub struct AccessNet: u64 {
const BindTcp = uapi::LANDLOCK_ACCESS_NET_BIND_TCP as u64;
const ConnectTcp = uapi::LANDLOCK_ACCESS_NET_CONNECT_TCP as u64;
}
}
impl TailoredCompatLevel for AccessNet {}
impl Access for AccessNet {
fn from_all(abi: ABI) -> Self {
match abi {
ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 => AccessNet::EMPTY,
ABI::V4 | ABI::V5 | ABI::V6 | ABI::V7 | ABI::V8 => {
AccessNet::BindTcp | AccessNet::ConnectTcp
}
}
}
}
impl PrivateAccess for AccessNet {
fn is_empty(self) -> bool {
AccessNet::is_empty(&self)
}
fn ruleset_handle_access(
ruleset: &mut Ruleset,
access: Self,
) -> Result<(), HandleAccessesError> {
ruleset.requested_handled_net |= access;
ruleset.actual_handled_net |= match access
.try_compat(
ruleset.compat.abi(),
ruleset.compat.level,
&mut ruleset.compat.state,
)
.map_err(HandleAccessError::Compat)?
{
Some(a) => a,
None => return Ok(()),
};
Ok(())
}
fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
AddRulesError::Net(error)
}
fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {
HandleAccessesError::Net(error)
}
}
#[derive(Debug)]
pub struct NetPort {
attr: uapi::landlock_net_port_attr,
port: u16,
allowed_access: AccessNet,
compat_level: Option<CompatLevel>,
}
impl NetPort {
pub fn new<A>(port: u16, access: A) -> Self
where
A: Into<AccessNet>,
{
NetPort {
attr: unsafe { zeroed() },
port,
allowed_access: access.into(),
compat_level: None,
}
}
}
impl Rule<AccessNet> for NetPort {}
impl PrivateRule<AccessNet> for NetPort {
const TYPE_ID: uapi::landlock_rule_type = uapi::landlock_rule_type_LANDLOCK_RULE_NET_PORT;
fn as_ptr(&mut self) -> *const libc::c_void {
self.attr.port = self.port as u64;
self.attr.allowed_access = self.allowed_access.bits();
&self.attr as *const _ as _
}
fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError> {
if ruleset.requested_handled_net.contains(self.allowed_access) {
Ok(())
} else {
Err(AddRuleError::UnhandledAccess {
access: self.allowed_access,
incompatible: self.allowed_access & !ruleset.requested_handled_net,
}
.into())
}
}
}
#[test]
fn net_port_check_consistency() {
use crate::landlock::*;
let bind = AccessNet::BindTcp;
let bind_connect = bind | AccessNet::ConnectTcp;
assert!(matches!(
Ruleset::from(ABI::Unsupported)
.handle_access(bind)
.unwrap()
.create()
.unwrap()
.add_rule(NetPort::new(1, bind_connect))
.unwrap_err(),
RulesetError::AddRules(AddRulesError::Net(AddRuleError::UnhandledAccess { access, incompatible }))
if access == bind_connect && incompatible == AccessNet::ConnectTcp
));
}
impl TryCompat<AccessNet> for NetPort {
fn try_compat_children<L>(
mut self,
abi: ABI,
parent_level: L,
compat_state: &mut CompatState,
) -> Result<Option<Self>, CompatError<AccessNet>>
where
L: Into<CompatLevel>,
{
self.allowed_access = match self.allowed_access.try_compat(
abi,
self.tailored_compat_level(parent_level),
compat_state,
)? {
Some(a) => a,
None => return Ok(None),
};
Ok(Some(self))
}
fn try_compat_inner(
&mut self,
_abi: ABI,
) -> Result<CompatResult<AccessNet>, CompatError<AccessNet>> {
Ok(CompatResult::Full)
}
}
impl OptionCompatLevelMut for NetPort {
fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
&mut self.compat_level
}
}
impl OptionCompatLevelMut for &mut NetPort {
fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
&mut self.compat_level
}
}
impl Compatible for NetPort {}
impl Compatible for &mut NetPort {}
#[cfg(test)]
mod tests {
use super::*;
use crate::landlock::*;
#[test]
fn test_access_net_from_all_1() {
assert_eq!(AccessNet::from_all(ABI::Unsupported), AccessNet::EMPTY);
}
#[test]
fn test_access_net_from_all_2() {
assert_eq!(AccessNet::from_all(ABI::V1), AccessNet::EMPTY);
}
#[test]
fn test_access_net_from_all_3() {
assert_eq!(AccessNet::from_all(ABI::V2), AccessNet::EMPTY);
}
#[test]
fn test_access_net_from_all_4() {
assert_eq!(AccessNet::from_all(ABI::V3), AccessNet::EMPTY);
}
#[test]
fn test_access_net_from_all_5() {
let expected = AccessNet::BindTcp | AccessNet::ConnectTcp;
assert_eq!(AccessNet::from_all(ABI::V4), expected);
}
#[test]
fn test_access_net_from_all_6() {
let expected = AccessNet::BindTcp | AccessNet::ConnectTcp;
assert_eq!(AccessNet::from_all(ABI::V5), expected);
}
#[test]
fn test_access_net_from_all_7() {
let expected = AccessNet::BindTcp | AccessNet::ConnectTcp;
assert_eq!(AccessNet::from_all(ABI::V6), expected);
}
#[test]
fn test_access_net_from_all_8() {
let expected = AccessNet::BindTcp | AccessNet::ConnectTcp;
assert_eq!(AccessNet::from_all(ABI::V7), expected);
}
#[test]
fn test_is_empty_1() {
assert!(PrivateAccess::is_empty(AccessNet::EMPTY));
}
#[test]
fn test_is_empty_2() {
assert!(!PrivateAccess::is_empty(AccessNet::BindTcp));
}
#[test]
fn test_is_empty_3() {
assert!(!PrivateAccess::is_empty(
AccessNet::BindTcp | AccessNet::ConnectTcp
));
}
#[test]
fn test_into_add_rules_error_1() {
let err = AddRuleError::UnhandledAccess {
access: AccessNet::BindTcp,
incompatible: AccessNet::BindTcp,
};
assert!(matches!(
AccessNet::into_add_rules_error(err),
AddRulesError::Net(AddRuleError::UnhandledAccess { .. })
));
}
#[test]
fn test_into_handle_accesses_error_1() {
let err = HandleAccessError::Compat(CompatError::Access(AccessError::Empty));
assert!(matches!(
AccessNet::into_handle_accesses_error(err),
HandleAccessesError::Net(HandleAccessError::Compat(CompatError::Access(
AccessError::Empty
)))
));
}
#[test]
fn test_net_port_new_1() {
let port = NetPort::new(80, AccessNet::BindTcp);
assert_eq!(port.port, 80);
assert_eq!(port.allowed_access, AccessNet::BindTcp);
assert!(port.compat_level.is_none());
}
#[test]
fn test_net_port_new_2() {
let port = NetPort::new(0, AccessNet::BindTcp | AccessNet::ConnectTcp);
assert_eq!(port.port, 0);
assert_eq!(
port.allowed_access,
AccessNet::BindTcp | AccessNet::ConnectTcp
);
}
#[test]
fn test_option_compat_level_mut_1() {
let mut port = NetPort::new(443, AccessNet::BindTcp);
assert!(port.as_option_compat_level_mut().is_none());
*port.as_option_compat_level_mut() = Some(CompatLevel::BestEffort);
assert_eq!(
*port.as_option_compat_level_mut(),
Some(CompatLevel::BestEffort)
);
}
#[test]
fn test_option_compat_level_mut_2() {
let mut port = NetPort::new(443, AccessNet::BindTcp);
let port_ref = &mut port;
assert!(port_ref.as_option_compat_level_mut().is_none());
*port_ref.as_option_compat_level_mut() = Some(CompatLevel::HardRequirement);
assert_eq!(
*port_ref.as_option_compat_level_mut(),
Some(CompatLevel::HardRequirement)
);
}
#[test]
fn test_try_compat_inner_1() {
let mut port = NetPort::new(80, AccessNet::BindTcp);
let result = port.try_compat_inner(ABI::V4);
assert!(matches!(result, Ok(CompatResult::Full)));
}
#[test]
fn test_try_compat_inner_2() {
let mut port = NetPort::new(80, AccessNet::ConnectTcp);
let result = port.try_compat_inner(ABI::Unsupported);
assert!(matches!(result, Ok(CompatResult::Full)));
}
}