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