netstack3_filter/
state.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5pub mod validation;
6
7use alloc::format;
8use alloc::string::ToString as _;
9use alloc::sync::Arc;
10use alloc::vec::Vec;
11use core::fmt::Debug;
12use core::hash::{Hash, Hasher};
13use core::num::NonZeroU16;
14use core::ops::RangeInclusive;
15
16use derivative::Derivative;
17use net_types::ip::{GenericOverIp, Ip};
18use netstack3_base::{
19    CoreTimerContext, Inspectable, InspectableValue, Inspector as _, MarkDomain,
20    MatcherBindingsTypes,
21};
22use packet_formats::ip::IpExt;
23
24use crate::actions::MarkAction;
25use crate::conntrack::{self, ConnectionDirection};
26use crate::context::{FilterBindingsContext, FilterBindingsTypes};
27use crate::logic::FilterTimerId;
28use crate::logic::nat::NatConfig;
29use crate::matchers::PacketMatcher;
30use crate::state::validation::ValidRoutines;
31
32/// The action to take on a packet.
33#[derive(Derivative)]
34#[derivative(Clone(bound = "RuleInfo: Clone"), Debug(bound = ""))]
35pub enum Action<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> {
36    /// Accept the packet.
37    ///
38    /// This is a terminal action for the current *installed* routine, i.e. no
39    /// further rules will be evaluated for this packet in the installed routine
40    /// (or any subroutines) in which this rule is installed. Subsequent
41    /// routines installed on the same hook will still be evaluated.
42    Accept,
43    /// Drop the packet.
44    ///
45    /// This is a terminal action for the current hook, i.e. no further rules
46    /// will be evaluated for this packet, even in other routines on the same
47    /// hook.
48    Drop,
49    /// Jump from the current routine to the specified uninstalled routine.
50    Jump(UninstalledRoutine<I, BT, RuleInfo>),
51    /// Stop evaluation of the current routine and return to the calling routine
52    /// (the routine from which the current routine was jumped), continuing
53    /// evaluation at the next rule.
54    ///
55    /// If invoked in an installed routine, equivalent to `Accept`, given
56    /// packets are accepted by default in the absence of any matching rules.
57    Return,
58    /// Redirect the packet to a local socket without changing the packet header
59    /// in any way.
60    ///
61    /// This is a terminal action for the current hook, i.e. no further rules
62    /// will be evaluated for this packet, even in other routines on the same
63    /// hook. However, note that this does not preclude actions on *other* hooks
64    /// from having an effect on this packet; for example, a packet that hits
65    /// TransparentProxy in INGRESS could still be dropped in LOCAL_INGRESS.
66    ///
67    /// This action is only valid in the INGRESS hook. This action is also only
68    /// valid in a rule that ensures the presence of a TCP or UDP header by
69    /// matching on the transport protocol, so that the packet can be properly
70    /// dispatched.
71    ///
72    /// Also note that transparently proxied packets will only be delivered to
73    /// sockets with the transparent socket option enabled.
74    TransparentProxy(TransparentProxy<I>),
75    /// A special case of destination NAT (DNAT) that redirects the packet to
76    /// the local host.
77    ///
78    /// This is a terminal action for all NAT routines on the current hook. The
79    /// packet is redirected by rewriting the destination IP address to one
80    /// owned by the ingress interface (if operating on incoming traffic in
81    /// INGRESS) or the loopback address (if operating on locally-generated
82    /// traffic in LOCAL_EGRESS). If this rule is installed on INGRESS and no IP
83    /// address is assigned to the incoming interface, the packet is dropped.
84    ///
85    /// As with all DNAT actions, this action is only valid in the INGRESS and
86    /// LOCAL_EGRESS hooks. If a destination port is specified, this action is
87    /// only valid in a rule that ensures the presence of a TCP or UDP header by
88    /// matching on the transport protocol, so that the destination port can be
89    /// rewritten.
90    ///
91    /// This is analogous to the `redirect` statement in Netfilter.
92    Redirect {
93        /// The optional range of destination ports used to rewrite the packet.
94        ///
95        /// If specified, the destination port of the packet will be rewritten
96        /// to some randomly chosen port in the range. If absent, the
97        /// destination port of the packet will not be rewritten.
98        dst_port: Option<RangeInclusive<NonZeroU16>>,
99    },
100    /// A special case of source NAT (SNAT) that reassigns the source IP address
101    /// of the packet to an address that is assigned to the outgoing interface.
102    ///
103    /// This is a terminal action for all NAT routines on the current hook. If
104    /// no address is assigned to the outgoing interface, the packet will be
105    /// dropped.
106    ///
107    /// This action is only valid in the EGRESS hook. If a source port range is
108    /// specified, this action is only valid in a rule that ensures the presence
109    /// of a TCP or UDP header by matching on the transport protocol, so that
110    /// the source port can be rewritten.
111    ///
112    /// This is analogous to the `masquerade` statement in Netfilter.
113    Masquerade {
114        /// The optional range of source ports used to rewrite the packet.
115        ///
116        /// The source port will be rewritten if necessary to ensure the
117        /// packet's flow does not conflict with an existing tracked connection.
118        /// Note that the source port may be rewritten whether or not this range
119        /// is specified.
120        ///
121        /// If specified, this overrides the default behavior and restricts the
122        /// range of possible values to which the source port can be rewritten.
123        src_port: Option<RangeInclusive<NonZeroU16>>,
124    },
125    /// Applies the mark action to the given mark domain.
126    ///
127    /// This is a non-terminal action for both routines and hooks. This is also
128    /// only available in [`IpRoutines`] because [`NatRoutines`] only runs on
129    /// the first packet in a connection and it is likely a misconfiguration
130    /// that packets after the first are marked differently or unmarked.
131    ///
132    /// Note: If we find use cases that justify this being in [`NatRoutines`] we
133    /// should relax this limitation and support it.
134    ///
135    /// This is analogous to the `mark` statement in Netfilter.
136    Mark {
137        /// The domain to apply the mark action.
138        domain: MarkDomain,
139        /// The action to apply.
140        action: MarkAction,
141    },
142}
143
144/// Transparently intercept the packet and deliver it to a local socket without
145/// changing the packet header.
146///
147/// When a local address is specified, it is the bound address of the local
148/// socket to redirect the packet to. When absent, the destination IP address of
149/// the packet is used for local delivery.
150///
151/// When a local port is specified, it is the bound port of the local socket to
152/// redirect the packet to. When absent, the destination port of the packet is
153/// used for local delivery.
154#[derive(Debug, Clone)]
155#[allow(missing_docs)]
156pub enum TransparentProxy<I: IpExt> {
157    LocalAddr(I::Addr),
158    LocalPort(NonZeroU16),
159    LocalAddrAndPort(I::Addr, NonZeroU16),
160}
161
162impl<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> Inspectable for Action<I, BT, RuleInfo> {
163    fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
164        let value = match self {
165            Self::Accept
166            | Self::Drop
167            | Self::Return
168            | Self::TransparentProxy(_)
169            | Self::Redirect { .. }
170            | Self::Masquerade { .. }
171            | Self::Mark { .. } => {
172                format!("{self:?}")
173            }
174            Self::Jump(UninstalledRoutine { routine: _, id }) => {
175                format!("Jump(UninstalledRoutine({id:?}))")
176            }
177        };
178        inspector.record_string("action", value);
179    }
180}
181
182/// A handle to a [`Routine`] that is not installed in a particular hook, and
183/// therefore is only run if jumped to from another routine.
184#[derive(Derivative)]
185#[derivative(Clone(bound = ""), Debug(bound = ""))]
186pub struct UninstalledRoutine<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> {
187    pub(crate) routine: Arc<Routine<I, BT, RuleInfo>>,
188    id: usize,
189}
190
191impl<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> UninstalledRoutine<I, BT, RuleInfo> {
192    /// Creates a new uninstalled routine with the provided contents.
193    pub fn new(rules: Vec<Rule<I, BT, RuleInfo>>, id: usize) -> Self {
194        Self { routine: Arc::new(Routine { rules }), id }
195    }
196
197    /// Returns the inner routine.
198    pub fn get(&self) -> &Routine<I, BT, RuleInfo> {
199        &*self.routine
200    }
201}
202
203impl<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> PartialEq
204    for UninstalledRoutine<I, BT, RuleInfo>
205{
206    fn eq(&self, other: &Self) -> bool {
207        let Self { routine: lhs, id: _ } = self;
208        let Self { routine: rhs, id: _ } = other;
209        Arc::ptr_eq(lhs, rhs)
210    }
211}
212
213impl<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> Eq for UninstalledRoutine<I, BT, RuleInfo> {}
214
215impl<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> Hash for UninstalledRoutine<I, BT, RuleInfo> {
216    fn hash<H: Hasher>(&self, state: &mut H) {
217        let Self { routine, id: _ } = self;
218        Arc::as_ptr(routine).hash(state)
219    }
220}
221
222impl<I: IpExt, BT: MatcherBindingsTypes> Inspectable for UninstalledRoutine<I, BT, ()> {
223    fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
224        let Self { routine, id } = self;
225        inspector.record_child(&id.to_string(), |inspector| {
226            inspector.delegate_inspectable(&**routine);
227        });
228    }
229}
230
231/// A set of criteria (matchers) and a resultant action to take if a given
232/// packet matches.
233#[derive(Derivative, GenericOverIp)]
234#[generic_over_ip(I, Ip)]
235#[derivative(Clone(bound = "RuleInfo: Clone"), Debug(bound = ""))]
236pub struct Rule<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> {
237    /// The criteria that a packet must match for the action to be executed.
238    pub matcher: PacketMatcher<I, BT>,
239    /// The action to take on a matching packet.
240    pub action: Action<I, BT, RuleInfo>,
241    /// Opaque information about this rule for use when validating and
242    /// converting state provided by Bindings into Core filtering state. This is
243    /// only used when installing filtering state, and allows Core to report to
244    /// Bindings which rule caused a particular error. It is zero-sized for
245    /// validated state.
246    #[derivative(Debug = "ignore")]
247    pub validation_info: RuleInfo,
248}
249
250impl<I: IpExt, BT: MatcherBindingsTypes> Inspectable for Rule<I, BT, ()> {
251    fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
252        let Self { matcher, action, validation_info: () } = self;
253        inspector.record_child("matchers", |inspector| {
254            let PacketMatcher {
255                in_interface,
256                out_interface,
257                src_address,
258                dst_address,
259                transport_protocol,
260                external_matcher,
261            } = matcher;
262
263            fn record_matcher<Inspector: netstack3_base::Inspector, M: InspectableValue>(
264                inspector: &mut Inspector,
265                name: &str,
266                matcher: &Option<M>,
267            ) {
268                if let Some(matcher) = matcher {
269                    inspector.record_inspectable_value(name, matcher);
270                }
271            }
272
273            record_matcher(inspector, "in_interface", in_interface);
274            record_matcher(inspector, "out_interface", out_interface);
275            record_matcher(inspector, "src_address", src_address);
276            record_matcher(inspector, "dst_address", dst_address);
277            record_matcher(inspector, "transport_protocol", transport_protocol);
278            record_matcher(inspector, "external_matcher", external_matcher);
279        });
280        inspector.delegate_inspectable(action);
281    }
282}
283
284/// A sequence of [`Rule`]s.
285#[derive(Derivative, GenericOverIp)]
286#[generic_over_ip(I, Ip)]
287#[derivative(Clone(bound = "RuleInfo: Clone"), Debug(bound = ""))]
288pub struct Routine<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> {
289    /// The rules to be executed in order.
290    pub rules: Vec<Rule<I, BT, RuleInfo>>,
291}
292
293impl<I: IpExt, BT: MatcherBindingsTypes> Inspectable for Routine<I, BT, ()> {
294    fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
295        let Self { rules } = self;
296        inspector.record_usize("rules", rules.len());
297        for rule in rules {
298            inspector.record_unnamed_child(|inspector| inspector.delegate_inspectable(rule));
299        }
300    }
301}
302
303/// A particular entry point for packet processing in which filtering routines
304/// are installed.
305#[derive(Derivative, GenericOverIp)]
306#[generic_over_ip(I, Ip)]
307#[derivative(Default(bound = ""), Debug(bound = ""))]
308pub struct Hook<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> {
309    /// The routines to be executed in order.
310    pub routines: Vec<Routine<I, BT, RuleInfo>>,
311}
312
313impl<I: IpExt, BT: MatcherBindingsTypes> Inspectable for Hook<I, BT, ()> {
314    fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
315        let Self { routines } = self;
316        inspector.record_usize("routines", routines.len());
317        for routine in routines {
318            inspector.record_unnamed_child(|inspector| {
319                inspector.delegate_inspectable(routine);
320            });
321        }
322    }
323}
324
325/// Routines that perform ordinary IP filtering.
326#[derive(Derivative)]
327#[derivative(Default(bound = ""), Debug(bound = ""))]
328pub struct IpRoutines<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> {
329    /// Occurs for incoming traffic before a routing decision has been made.
330    pub ingress: Hook<I, BT, RuleInfo>,
331    /// Occurs for incoming traffic that is destined for the local host.
332    pub local_ingress: Hook<I, BT, RuleInfo>,
333    /// Occurs for incoming traffic that is destined for another node.
334    pub forwarding: Hook<I, BT, RuleInfo>,
335    /// Occurs for locally-generated traffic before a final routing decision has
336    /// been made.
337    pub local_egress: Hook<I, BT, RuleInfo>,
338    /// Occurs for all outgoing traffic after a routing decision has been made.
339    pub egress: Hook<I, BT, RuleInfo>,
340}
341
342impl<I: IpExt, BT: MatcherBindingsTypes> Inspectable for IpRoutines<I, BT, ()> {
343    fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
344        let Self { ingress, local_ingress, forwarding, local_egress, egress } = self;
345
346        inspector.record_child("ingress", |inspector| inspector.delegate_inspectable(ingress));
347        inspector.record_child("local_ingress", |inspector| {
348            inspector.delegate_inspectable(local_ingress)
349        });
350        inspector
351            .record_child("forwarding", |inspector| inspector.delegate_inspectable(forwarding));
352        inspector
353            .record_child("local_egress", |inspector| inspector.delegate_inspectable(local_egress));
354        inspector.record_child("egress", |inspector| inspector.delegate_inspectable(egress));
355    }
356}
357
358/// Routines that can perform NAT.
359///
360/// Note that NAT routines are only executed *once* for a given connection, for
361/// the first packet in the flow.
362#[derive(Derivative)]
363#[derivative(Default(bound = ""), Debug(bound = ""))]
364pub struct NatRoutines<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> {
365    /// Occurs for incoming traffic before a routing decision has been made.
366    pub ingress: Hook<I, BT, RuleInfo>,
367    /// Occurs for incoming traffic that is destined for the local host.
368    pub local_ingress: Hook<I, BT, RuleInfo>,
369    /// Occurs for locally-generated traffic before a final routing decision has
370    /// been made.
371    pub local_egress: Hook<I, BT, RuleInfo>,
372    /// Occurs for all outgoing traffic after a routing decision has been made.
373    pub egress: Hook<I, BT, RuleInfo>,
374}
375
376impl<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> NatRoutines<I, BT, RuleInfo> {
377    pub(crate) fn contains_rules(&self) -> bool {
378        let Self { ingress, local_ingress, local_egress, egress } = self;
379
380        let hook_contains_rules =
381            |hook: &Hook<_, _, _>| hook.routines.iter().any(|routine| !routine.rules.is_empty());
382        hook_contains_rules(&ingress)
383            || hook_contains_rules(&local_ingress)
384            || hook_contains_rules(&local_egress)
385            || hook_contains_rules(&egress)
386    }
387}
388
389impl<I: IpExt, BT: MatcherBindingsTypes> Inspectable for NatRoutines<I, BT, ()> {
390    fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
391        let Self { ingress, local_ingress, local_egress, egress } = self;
392
393        inspector.record_child("ingress", |inspector| inspector.delegate_inspectable(ingress));
394        inspector.record_child("local_ingress", |inspector| {
395            inspector.delegate_inspectable(local_ingress)
396        });
397        inspector
398            .record_child("local_egress", |inspector| inspector.delegate_inspectable(local_egress));
399        inspector.record_child("egress", |inspector| inspector.delegate_inspectable(egress));
400    }
401}
402
403/// IP version-specific filtering routine state.
404#[derive(Derivative, GenericOverIp)]
405#[generic_over_ip(I, Ip)]
406#[derivative(Default(bound = ""), Debug(bound = ""))]
407pub struct Routines<I: IpExt, BT: MatcherBindingsTypes, RuleInfo> {
408    /// Routines that perform IP filtering.
409    pub ip: IpRoutines<I, BT, RuleInfo>,
410    /// Routines that perform IP filtering and NAT.
411    pub nat: NatRoutines<I, BT, RuleInfo>,
412}
413
414/// A one-way boolean toggle that can only go from `false` to `true`.
415///
416/// Once it has been flipped to `true`, it will remain in that state forever.
417#[derive(Default)]
418pub struct OneWayBoolean(bool);
419
420impl OneWayBoolean {
421    /// A [`OneWayBoolean`] that is enabled on creation.
422    pub const TRUE: Self = Self(true);
423
424    /// Get the value of the boolean.
425    pub fn get(&self) -> bool {
426        let Self(inner) = self;
427        *inner
428    }
429
430    /// Toggle the boolean to `true`.
431    ///
432    /// This operation is idempotent: even though the [`OneWayBoolean`]'s value will
433    /// only ever change from `false` to `true` once, this method can be called any
434    /// number of times safely and the value will remain `true`.
435    pub fn set(&mut self) {
436        let Self(inner) = self;
437        *inner = true;
438    }
439}
440
441/// IP version-specific filtering state.
442pub struct State<I: IpExt, A, BT: FilterBindingsTypes> {
443    /// Routines used for filtering packets that are installed on hooks.
444    pub installed_routines: ValidRoutines<I, BT>,
445    /// Routines that are only executed if jumped to from other routines.
446    ///
447    /// Jump rules refer to their targets by holding a reference counted pointer
448    /// to the inner routine; we hold this index of all uninstalled routines
449    /// that have any references in order to report them in inspect data.
450    pub(crate) uninstalled_routines: Vec<UninstalledRoutine<I, BT, ()>>,
451    /// Connection tracking state.
452    pub conntrack: conntrack::Table<I, NatConfig<I, A>, BT>,
453    /// One-way boolean toggle indicating whether any rules have ever been added to
454    /// an installed NAT routine. If not, performing NAT can safely be skipped.
455    ///
456    /// This is useful because if any NAT is being performed, we have to check
457    /// whether it's necessary to perform implicit NAT for *all* traffic -- even if
458    /// it doesn't match any NAT rules -- to avoid conflicting tracked connections.
459    /// If we know that no NAT is being performed at all, this extra work can be
460    /// avoided.
461    ///
462    /// Note that this value will only ever go from false to true; it does not
463    /// indicate whether any NAT rules are *currently* installed. This avoids a race
464    /// condition where NAT rules are removed but connections are still being NATed
465    /// based on those rules, and therefore must be considered when creating new
466    /// connection tracking entries.
467    pub nat_installed: OneWayBoolean,
468}
469
470impl<I: IpExt, A, BC: FilterBindingsContext> State<I, A, BC> {
471    /// Create a new State.
472    pub fn new<CC: CoreTimerContext<FilterTimerId<I>, BC>>(bindings_ctx: &mut BC) -> Self {
473        Self {
474            installed_routines: Default::default(),
475            uninstalled_routines: Default::default(),
476            conntrack: conntrack::Table::new::<CC>(bindings_ctx),
477            nat_installed: OneWayBoolean::default(),
478        }
479    }
480}
481
482impl<I: IpExt, A: InspectableValue, BT: FilterBindingsTypes> Inspectable for State<I, A, BT> {
483    fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
484        let Self { installed_routines, uninstalled_routines, conntrack, nat_installed: _ } = self;
485        let Routines { ip, nat } = installed_routines.get();
486
487        inspector.record_child("IP", |inspector| inspector.delegate_inspectable(ip));
488        inspector.record_child("NAT", |inspector| inspector.delegate_inspectable(nat));
489        inspector.record_child("uninstalled", |inspector| {
490            inspector.record_usize("routines", uninstalled_routines.len());
491            for routine in uninstalled_routines {
492                inspector.delegate_inspectable(routine);
493            }
494        });
495
496        inspector.record_child("conntrack", |inspector| {
497            inspector.delegate_inspectable(conntrack);
498        });
499    }
500}
501
502/// A trait for interacting with the pieces of packet metadata that are
503/// important for filtering.
504pub trait FilterIpMetadata<I: IpExt, A, BT: FilterBindingsTypes>: FilterMarkMetadata {
505    /// Removes the conntrack connection and packet direction, if they exist.
506    fn take_connection_and_direction(
507        &mut self,
508    ) -> Option<(conntrack::Connection<I, NatConfig<I, A>, BT>, ConnectionDirection)>;
509
510    /// Puts a new conntrack connection and packet direction into the metadata
511    /// struct, returning the previous connection value, if one existed.
512    fn replace_connection_and_direction(
513        &mut self,
514        conn: conntrack::Connection<I, NatConfig<I, A>, BT>,
515        direction: ConnectionDirection,
516    ) -> Option<conntrack::Connection<I, NatConfig<I, A>, BT>>;
517}
518
519/// A trait for interacting with packet mark metadata.
520//
521// The reason why we split this trait from the `FilterIpMetadata` is to avoid
522// introducing trait bounds and type parameters into methods that only need
523// to change the mark, for example, all the `check_routine*` methods. Those
524// methods does not need the ability to take conntrack related information. This
525// becomes a meaningful simplification for those cases.
526pub trait FilterMarkMetadata {
527    /// Applies the mark action to the metadata.
528    fn apply_mark_action(&mut self, domain: MarkDomain, action: MarkAction);
529}