blob: 8ab537b0ab32ece624dd7ee0f9cdaeb61b9662b6 [file] [log] [blame]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
pub mod validation;
use alloc::{format, string::ToString as _, sync::Arc, vec::Vec};
use core::{
fmt::Debug,
hash::{Hash, Hasher},
};
use derivative::Derivative;
use net_types::ip::{GenericOverIp, Ip};
use netstack3_base::{Inspectable, Inspector as _};
use packet_formats::ip::IpExt;
use crate::{conntrack, context::FilterBindingsTypes, matchers::PacketMatcher, ValidRoutines};
/// The action to take on a packet.
#[derive(Derivative)]
#[derivative(
Clone(bound = "DeviceClass: Clone, RuleInfo: Clone"),
Debug(bound = "DeviceClass: Debug")
)]
pub enum Action<I: IpExt, DeviceClass, RuleInfo> {
/// Accept the packet.
///
/// This is a terminal action for the current *installed* routine, i.e. no
/// further rules will be evaluated for this packet in the installed routine
/// (or any subroutines) in which this rule is installed. Subsequent
/// routines installed on the same hook will still be evaluated.
Accept,
/// Drop the packet.
///
/// This is a terminal action for the current hook, i.e. no further rules
/// will be evaluated for this packet, even in other routines on the same
/// hook.
Drop,
/// Jump from the current routine to the specified uninstalled routine.
Jump(UninstalledRoutine<I, DeviceClass, RuleInfo>),
/// Stop evaluation of the current routine and return to the calling routine
/// (the routine from which the current routine was jumped), continuing
/// evaluation at the next rule.
///
/// If invoked in an installed routine, equivalent to `Accept`, given
/// packets are accepted by default in the absence of any matching rules.
Return,
}
impl<I: IpExt, DeviceClass: Debug> Inspectable for Action<I, DeviceClass, ()> {
fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
let value = match self {
Self::Accept | Self::Drop | Self::Return => format!("{self:?}"),
Self::Jump(UninstalledRoutine { routine: _, id }) => {
format!("Jump(UninstalledRoutine({id:?}))")
}
};
inspector.record_string("action", value);
}
}
/// A handle to a [`Routine`] that is not installed in a particular hook, and
/// therefore is only run if jumped to from another routine.
#[derive(Derivative)]
#[derivative(Clone(bound = ""), Debug(bound = "DeviceClass: Debug"))]
pub struct UninstalledRoutine<I: IpExt, DeviceClass, RuleInfo> {
pub(crate) routine: Arc<Routine<I, DeviceClass, RuleInfo>>,
id: usize,
}
impl<I: IpExt, DeviceClass, RuleInfo> UninstalledRoutine<I, DeviceClass, RuleInfo> {
/// Creates a new uninstalled routine with the provided contents.
pub fn new(rules: Vec<Rule<I, DeviceClass, RuleInfo>>, id: usize) -> Self {
Self { routine: Arc::new(Routine { rules }), id }
}
/// Returns the inner routine.
pub fn get(&self) -> &Routine<I, DeviceClass, RuleInfo> {
&*self.routine
}
}
impl<I: IpExt, DeviceClass, RuleInfo> PartialEq for UninstalledRoutine<I, DeviceClass, RuleInfo> {
fn eq(&self, other: &Self) -> bool {
let Self { routine: lhs, id: _ } = self;
let Self { routine: rhs, id: _ } = other;
Arc::ptr_eq(lhs, rhs)
}
}
impl<I: IpExt, DeviceClass, RuleInfo> Eq for UninstalledRoutine<I, DeviceClass, RuleInfo> {}
impl<I: IpExt, DeviceClass, RuleInfo> Hash for UninstalledRoutine<I, DeviceClass, RuleInfo> {
fn hash<H: Hasher>(&self, state: &mut H) {
let Self { routine, id: _ } = self;
Arc::as_ptr(routine).hash(state)
}
}
impl<I: IpExt, DeviceClass: Debug> Inspectable for UninstalledRoutine<I, DeviceClass, ()> {
fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
let Self { routine, id } = self;
inspector.record_child(&id.to_string(), |inspector| {
inspector.delegate_inspectable(&**routine);
});
}
}
/// A set of criteria (matchers) and a resultant action to take if a given
/// packet matches.
#[derive(Derivative, GenericOverIp)]
#[generic_over_ip(I, Ip)]
#[derivative(
Clone(bound = "DeviceClass: Clone, RuleInfo: Clone"),
Debug(bound = "DeviceClass: Debug")
)]
pub struct Rule<I: IpExt, DeviceClass, RuleInfo> {
/// The criteria that a packet must match for the action to be executed.
pub matcher: PacketMatcher<I, DeviceClass>,
/// The action to take on a matching packet.
pub action: Action<I, DeviceClass, RuleInfo>,
/// Opaque information about this rule for use when validating and
/// converting state provided by Bindings into Core filtering state. This is
/// only used when installing filtering state, and allows Core to report to
/// Bindings which rule caused a particular error. It is zero-sized for
/// validated state.
#[derivative(Debug = "ignore")]
pub validation_info: RuleInfo,
}
impl<I: IpExt, DeviceClass: Debug> Inspectable for Rule<I, DeviceClass, ()> {
fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
let Self { matcher, action, validation_info: () } = self;
inspector.record_child("matchers", |inspector| {
let PacketMatcher {
in_interface,
out_interface,
src_address,
dst_address,
transport_protocol,
} = matcher;
fn record_matcher<Inspector: netstack3_base::Inspector, M: Debug>(
inspector: &mut Inspector,
name: &str,
matcher: &Option<M>,
) {
if let Some(matcher) = matcher {
inspector.record_string(name, format!("{matcher:?}"))
}
}
record_matcher(inspector, "in_interface", in_interface);
record_matcher(inspector, "out_interface", out_interface);
record_matcher(inspector, "src_address", src_address);
record_matcher(inspector, "dst_address", dst_address);
record_matcher(inspector, "transport_protocol", transport_protocol);
});
inspector.delegate_inspectable(action);
}
}
/// A sequence of [`Rule`]s.
#[derive(Derivative, GenericOverIp)]
#[generic_over_ip(I, Ip)]
#[derivative(
Clone(bound = "DeviceClass: Clone, RuleInfo: Clone"),
Debug(bound = "DeviceClass: Debug")
)]
pub struct Routine<I: IpExt, DeviceClass, RuleInfo> {
/// The rules to be executed in order.
pub rules: Vec<Rule<I, DeviceClass, RuleInfo>>,
}
impl<I: IpExt, DeviceClass: Debug> Inspectable for Routine<I, DeviceClass, ()> {
fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
let Self { rules } = self;
inspector.record_usize("rules", rules.len());
for rule in rules {
inspector.record_unnamed_child(|inspector| inspector.delegate_inspectable(rule));
}
}
}
/// A particular entry point for packet processing in which filtering routines
/// are installed.
#[derive(Derivative, GenericOverIp)]
#[generic_over_ip(I, Ip)]
#[derivative(Default(bound = ""), Debug(bound = "DeviceClass: Debug"))]
pub struct Hook<I: IpExt, DeviceClass, RuleInfo> {
/// The routines to be executed in order.
pub routines: Vec<Routine<I, DeviceClass, RuleInfo>>,
}
impl<I: IpExt, DeviceClass: Debug> Inspectable for Hook<I, DeviceClass, ()> {
fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
let Self { routines } = self;
inspector.record_usize("routines", routines.len());
for routine in routines {
inspector.record_unnamed_child(|inspector| {
inspector.delegate_inspectable(routine);
});
}
}
}
/// Routines that perform ordinary IP filtering.
#[derive(Derivative)]
#[derivative(Default(bound = ""), Debug(bound = "DeviceClass: Debug"))]
pub struct IpRoutines<I: IpExt, DeviceClass, RuleInfo> {
/// Occurs for incoming traffic before a routing decision has been made.
pub ingress: Hook<I, DeviceClass, RuleInfo>,
/// Occurs for incoming traffic that is destined for the local host.
pub local_ingress: Hook<I, DeviceClass, RuleInfo>,
/// Occurs for incoming traffic that is destined for another node.
pub forwarding: Hook<I, DeviceClass, RuleInfo>,
/// Occurs for locally-generated traffic before a final routing decision has
/// been made.
pub local_egress: Hook<I, DeviceClass, RuleInfo>,
/// Occurs for all outgoing traffic after a routing decision has been made.
pub egress: Hook<I, DeviceClass, RuleInfo>,
}
/// Routines that can perform NAT.
///
/// Note that NAT routines are only executed *once* for a given connection, for
/// the first packet in the flow.
#[derive(Derivative)]
#[derivative(Default(bound = ""), Debug(bound = "DeviceClass: Debug"))]
pub struct NatRoutines<I: IpExt, DeviceClass, RuleInfo> {
/// Occurs for incoming traffic before a routing decision has been made.
pub ingress: Hook<I, DeviceClass, RuleInfo>,
/// Occurs for incoming traffic that is destined for the local host.
pub local_ingress: Hook<I, DeviceClass, RuleInfo>,
/// Occurs for locally-generated traffic before a final routing decision has
/// been made.
pub local_egress: Hook<I, DeviceClass, RuleInfo>,
/// Occurs for all outgoing traffic after a routing decision has been made.
pub egress: Hook<I, DeviceClass, RuleInfo>,
}
/// Data stored in [`conntrack::Connection`] that is only needed by filtering.
#[derive(Default)]
pub struct ConntrackExternalData {}
/// IP version-specific filtering routine state.
#[derive(Derivative, GenericOverIp)]
#[generic_over_ip(I, Ip)]
#[derivative(Default(bound = ""), Debug(bound = "DeviceClass: Debug"))]
pub struct Routines<I: IpExt, DeviceClass, RuleInfo> {
/// Routines that perform IP filtering.
pub ip: IpRoutines<I, DeviceClass, RuleInfo>,
/// Routines that perform IP filtering and NAT.
pub nat: NatRoutines<I, DeviceClass, RuleInfo>,
}
/// IP version-specific filtering state.
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct State<I: IpExt, BT: FilterBindingsTypes> {
/// Routines used for filtering packets that are installed on hooks.
pub installed_routines: ValidRoutines<I, BT::DeviceClass>,
/// Routines that are only executed if jumped to from other routines.
///
/// Jump rules refer to their targets by holding a reference counted pointer
/// to the inner routine; we hold this index of all uninstalled routines
/// that have any references in order to report them in inspect data.
pub(crate) uninstalled_routines: Vec<UninstalledRoutine<I, BT::DeviceClass, ()>>,
/// Connection tracking state.
#[allow(dead_code)]
pub(crate) conntrack: conntrack::Table<I, BT, ConntrackExternalData>,
}
impl<I: IpExt, BT: FilterBindingsTypes> Inspectable for State<I, BT> {
fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
let Self { installed_routines, uninstalled_routines, conntrack: _ } = self;
// TODO(https://fxbug.dev/318717702): when we implement NAT, report NAT
// routines in inspect data.
let Routines { ip, nat: _ } = installed_routines.get();
let IpRoutines { ingress, local_ingress, forwarding, local_egress, egress } = ip;
inspector.record_child("ingress", |inspector| inspector.delegate_inspectable(ingress));
inspector.record_child("local_ingress", |inspector| {
inspector.delegate_inspectable(local_ingress)
});
inspector
.record_child("forwarding", |inspector| inspector.delegate_inspectable(forwarding));
inspector
.record_child("local_egress", |inspector| inspector.delegate_inspectable(local_egress));
inspector.record_child("egress", |inspector| inspector.delegate_inspectable(egress));
inspector.record_child("uninstalled", |inspector| {
inspector.record_usize("routines", uninstalled_routines.len());
for routine in uninstalled_routines {
inspector.delegate_inspectable(routine);
}
});
}
}
/// A trait for interacting with the pieces of packet metadata that are
/// important for filtering.
pub trait FilterIpMetadata<I: IpExt, BT: FilterBindingsTypes> {
/// Removes the conntrack connection, if it exists.
fn take_conntrack_connection(
&mut self,
) -> Option<conntrack::Connection<I, BT, ConntrackExternalData>>;
/// Puts a new conntrack connection into the metadata struct, returning the
/// previous value.
fn replace_conntrack_connection(
&mut self,
conn: conntrack::Connection<I, BT, ConntrackExternalData>,
) -> Option<conntrack::Connection<I, BT, ConntrackExternalData>>;
}