starnix_core/vfs/socket/
iptables_utils.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
5// This file contains translation between fuchsia.net.filter data structures and Linux
6// iptables structures.
7
8use bstr::BString;
9use itertools::Itertools;
10use starnix_logging::track_stub;
11use starnix_uapi::iptables_flags::{
12    IptIpFlags, IptIpFlagsV4, IptIpFlagsV6, IptIpInverseFlags, NfIpHooks, NfNatRangeFlags,
13    XtTcpInverseFlags, XtUdpInverseFlags,
14};
15use starnix_uapi::{
16    IPPROTO_ICMP, IPPROTO_ICMPV6, IPPROTO_IP, IPPROTO_TCP, IPPROTO_UDP, IPT_RETURN, NF_ACCEPT,
17    NF_DROP, NF_IP_FORWARD, NF_IP_LOCAL_IN, NF_IP_LOCAL_OUT, NF_IP_NUMHOOKS, NF_IP_POST_ROUTING,
18    NF_IP_PRE_ROUTING, NF_QUEUE, c_char, c_int, c_uchar, c_uint, in_addr, in6_addr, ip6t_entry,
19    ip6t_ip6, ip6t_replace, ipt_entry, ipt_ip, ipt_replace, nf_ip_hook_priorities_NF_IP_PRI_FILTER,
20    nf_ip_hook_priorities_NF_IP_PRI_MANGLE, nf_ip_hook_priorities_NF_IP_PRI_NAT_DST,
21    nf_ip_hook_priorities_NF_IP_PRI_NAT_SRC, nf_ip_hook_priorities_NF_IP_PRI_RAW,
22    nf_nat_ipv4_multi_range_compat, nf_nat_range, xt_bpf_info_v1,
23    xt_entry_match__bindgen_ty_1__bindgen_ty_1 as xt_entry_match,
24    xt_entry_target__bindgen_ty_1__bindgen_ty_1 as xt_entry_target, xt_mark_tginfo2, xt_tcp,
25    xt_tproxy_target_info_v1, xt_udp,
26};
27use std::any::type_name;
28use std::collections::{HashMap, HashSet};
29use std::ffi::CStr;
30use std::mem::size_of;
31use std::num::NonZeroU16;
32use std::ops::RangeInclusive;
33use thiserror::Error;
34use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
35
36use {
37    fidl_fuchsia_ebpf as febpf, fidl_fuchsia_net as fnet,
38    fidl_fuchsia_net_filter_ext as fnet_filter_ext,
39    fidl_fuchsia_net_matchers_ext as fnet_matchers_ext,
40};
41
42const TABLE_FILTER: &str = "filter";
43const TABLE_MANGLE: &str = "mangle";
44const TABLE_NAT: &str = "nat";
45const TABLE_RAW: &str = "raw";
46
47const CHAIN_PREROUTING: &str = "PREROUTING";
48const CHAIN_INPUT: &str = "INPUT";
49const CHAIN_FORWARD: &str = "FORWARD";
50const CHAIN_OUTPUT: &str = "OUTPUT";
51const CHAIN_POSTROUTING: &str = "POSTROUTING";
52
53const IPT_REPLACE_SIZE: usize = size_of::<ipt_replace>();
54const IP6T_REPLACE_SIZE: usize = size_of::<ip6t_replace>();
55
56// Verdict codes for Standard targets, calculated the same way as Linux.
57pub const VERDICT_DROP: i32 = -(NF_DROP as i32) - 1;
58pub const VERDICT_ACCEPT: i32 = -(NF_ACCEPT as i32) - 1;
59pub const VERDICT_QUEUE: i32 = -(NF_QUEUE as i32) - 1;
60pub const VERDICT_RETURN: i32 = IPT_RETURN;
61
62const TARGET_STANDARD: &str = "";
63const TARGET_ERROR: &str = "ERROR";
64const TARGET_REDIRECT: &str = "REDIRECT";
65const TARGET_TPROXY: &str = "TPROXY";
66const TARGET_MARK: &str = "MARK";
67
68#[derive(Debug, Error, PartialEq)]
69pub enum IpTableParseError {
70    #[error("error during ascii conversion: {0}")]
71    AsciiConversion(#[from] AsciiConversionError),
72    #[error("error during address conversion: {0}")]
73    IpAddressConversion(#[from] IpAddressConversionError),
74    #[error("FIDL conversion error: {0}")]
75    FidlConversion(#[from] fnet_filter_ext::FidlConversionError),
76    #[error("Port matcher error: {0}")]
77    PortMatcher(#[from] fnet_matchers_ext::PortError),
78    #[error("buffer of size {size} is too small to read ipt_replace or ip6t_replace")]
79    BufferTooSmallForMetadata { size: usize },
80    #[error("specified size {specified_size} does not match size of entries {entries_size}")]
81    SizeMismatch { specified_size: usize, entries_size: usize },
82    #[error("reached end of buffer while trying to parse {type_name} at position {position}")]
83    ParseEndOfBuffer { type_name: &'static str, position: usize },
84    #[error("reached end of buffer while advancing by {offset} at position {position}")]
85    AdvanceEndOfBuffer { offset: usize, position: usize },
86    #[error("target offset {offset} is too small to fit ipt_entry")]
87    TargetOffsetTooSmall { offset: usize },
88    #[error("matchers extend beyond target offset {offset}")]
89    InvalidTargetOffset { offset: usize },
90    #[error("next offset {offset} is too small to fit ipt_entry")]
91    NextOffsetTooSmall { offset: usize },
92    #[error("target extends beyond next offset {offset}")]
93    InvalidNextOffset { offset: usize },
94    #[error("match size {size} is too small to fit xt_entry_match")]
95    MatchSizeTooSmall { size: usize },
96    #[error("target size {size} does not match specified {match_name} matcher")]
97    MatchSizeMismatch { size: usize, match_name: &'static str },
98    #[error("target size {size} is too small to fit xt_entry_target")]
99    TargetSizeTooSmall { size: usize },
100    #[error("target size {size} does not match specified {target_name} target")]
101    TargetSizeMismatch { size: usize, target_name: &'static str },
102    #[error("specified {specified} entries but found {found} entries")]
103    NumEntriesMismatch { specified: usize, found: usize },
104    #[error("error entry has unexpected matchers")]
105    ErrorEntryHasMatchers,
106    #[error("table definition does not have trailing error target")]
107    NoTrailingErrorTarget,
108    #[error("found chain {chain_name} with no policy entry")]
109    ChainHasNoPolicy { chain_name: String },
110    #[error("found rule specification before first chain definition")]
111    RuleBeforeFirstChain,
112    #[error("invalid IP flags {flags:#x} found in rule specification")]
113    InvalidIpFlags { flags: u8 },
114    #[error("invalid IP inverse flags {flags:#x} found in rule specification")]
115    InvalidIpInverseFlags { flags: u8 },
116    #[error("invalid TCP matcher inverse flags {flags:#x}")]
117    InvalidXtTcpInverseFlags { flags: u8 },
118    #[error("invalid UDP matcher inverse flags {flags:#x}")]
119    InvalidXtUdpInverseFlags { flags: u8 },
120    #[error("invalid standard target verdict {verdict}")]
121    InvalidVerdict { verdict: i32 },
122    #[error("invalid jump target {jump_target}")]
123    InvalidJumpTarget { jump_target: usize },
124    #[error("invalid valid_hooks field {hooks:#x}")]
125    InvalidHookBits { hooks: u32 },
126    #[error("invalid redirect target range size {range_size}")]
127    InvalidRedirectTargetRangeSize { range_size: u32 },
128    #[error("invalid redirect target range [{start}, {end}]")]
129    InvalidRedirectTargetRange { start: u16, end: u16 },
130    #[error("invalid redirect target flags {flags}")]
131    InvalidRedirectTargetFlags { flags: u32 },
132    #[error("invalid tproxy target: one of address and port must be specified")]
133    InvalidTproxyZeroAddressAndPort,
134    #[error("invalid hooks {hooks:#x} found for table {table_name}")]
135    InvalidHooksForTable { hooks: u32, table_name: &'static str },
136    #[error("hook ranges overlap at offset {offset}")]
137    HookRangesOverlap { offset: usize },
138    #[error("unrecognized table name {table_name}")]
139    UnrecognizedTableName { table_name: String },
140    #[error("invalid hook entry or underflow values for index {index} [{start}, {end}]")]
141    InvalidHookEntryOrUnderflow { index: u32, start: usize, end: usize },
142    #[error("unexpected error target {error_name} found in installed routine")]
143    UnexpectedErrorTarget { error_name: String },
144    #[error("too many rules")]
145    TooManyRules,
146    #[error("match extension does not match protocol")]
147    MatchExtensionDoesNotMatchProtocol,
148    #[error("match extension would overwrite another matcher")]
149    MatchExtensionOverwrite,
150    #[error("unsupported BPF mode {mode}")]
151    UnsupportedBpfMatcherMode { mode: u16 },
152    #[error("eBPF program path is not null terminated")]
153    NoNulInEbpfProgramPath,
154    #[error("Invalid eBPF program path: {path}")]
155    InvalidEbpfProgramPath { path: BString },
156}
157
158#[derive(Debug, Error, PartialEq)]
159pub enum AsciiConversionError {
160    #[error("nul byte not found in ASCII string {chars:?}")]
161    NulByteNotFound { chars: Vec<c_char> },
162    #[error("unexpected nul byte found in UTF-8 String at position {pos}")]
163    NulByteInString { pos: usize },
164    #[error("char is out of range for ASCII (0 to 127)")]
165    NonAsciiChar,
166    #[error("buffer of size {buffer_size} too small to fit data of size {data_size}")]
167    BufferTooSmall { buffer_size: usize, data_size: usize },
168}
169
170#[derive(Debug, Error, PartialEq)]
171pub enum IpAddressConversionError {
172    #[error("IPv4 address subnet mask {mask:#b} has non-prefix bits")]
173    IpV4SubnetMaskHasNonPrefixBits { mask: u32 },
174    #[error("IPv6 address subnet mask {mask:#b} has non-prefix bits")]
175    IpV6SubnetMaskHasNonPrefixBits { mask: u128 },
176}
177
178/// Metadata passed by `iptables` when updating a table.
179///
180/// Describes the subsequent buffer of `size` bytes, containing `num_entries` Entries that describe
181/// chain definitions, rule specifications, and end of input.
182/// IPv4 and IPv6 tables have the same metadata but uses different Linux structs: `ipt_replace` for
183/// IPv4, and `ip6t_replace` for IPv6.
184#[derive(Clone, Debug)]
185pub struct ReplaceInfo {
186    /// The table to be replaced.
187    pub table_id: TableId,
188
189    /// Number of entries defined on the table.
190    pub num_entries: usize,
191
192    /// Size of entries in bytes.
193    pub size: usize,
194
195    /// Bitmap of which installed chains are on the table.
196    pub valid_hooks: NfIpHooks,
197
198    /// Byte offsets of the first entry of each installed chain.
199    pub hook_entry: [c_uint; 5usize],
200
201    /// Byte offsets of the policy of each installed chain.
202    pub underflow: [c_uint; 5usize],
203
204    /// Unused field. Number of counters.
205    pub num_counters: c_uint,
206}
207
208impl TryFrom<ipt_replace> for ReplaceInfo {
209    type Error = IpTableParseError;
210
211    fn try_from(replace: ipt_replace) -> Result<Self, Self::Error> {
212        let valid_hooks = NfIpHooks::from_bits(replace.valid_hooks)
213            .ok_or(IpTableParseError::InvalidHookBits { hooks: replace.valid_hooks })?;
214        Ok(Self {
215            table_id: TableId::try_from(&replace.name)?,
216            num_entries: usize::try_from(replace.num_entries).expect("u32 fits in usize"),
217            size: usize::try_from(replace.size).expect("u32 fits in usize"),
218            valid_hooks,
219            hook_entry: replace.hook_entry,
220            underflow: replace.underflow,
221            num_counters: replace.num_counters,
222        })
223    }
224}
225
226impl TryFrom<ip6t_replace> for ReplaceInfo {
227    type Error = IpTableParseError;
228
229    fn try_from(replace: ip6t_replace) -> Result<Self, Self::Error> {
230        let valid_hooks = NfIpHooks::from_bits(replace.valid_hooks)
231            .ok_or(IpTableParseError::InvalidHookBits { hooks: replace.valid_hooks })?;
232        Ok(Self {
233            table_id: TableId::try_from(&replace.name)?,
234            num_entries: usize::try_from(replace.num_entries).expect("u32 fits in usize"),
235            size: usize::try_from(replace.size).expect("u32 fits in usize"),
236            valid_hooks,
237            hook_entry: replace.hook_entry,
238            underflow: replace.underflow,
239            num_counters: replace.num_counters,
240        })
241    }
242}
243
244// Metadata of each Entry.
245#[derive(Clone, Debug)]
246pub struct EntryInfo {
247    pub ip_info: IpInfo,
248    pub target_offset: usize,
249    pub next_offset: usize,
250}
251
252impl TryFrom<ipt_entry> for EntryInfo {
253    type Error = IpTableParseError;
254
255    fn try_from(entry: ipt_entry) -> Result<Self, Self::Error> {
256        let ip_info = IpInfo::try_from(entry.ip)?;
257
258        let target_offset = usize::from(entry.target_offset);
259        if target_offset < size_of::<ipt_entry>() {
260            return Err(IpTableParseError::TargetOffsetTooSmall { offset: target_offset });
261        }
262
263        let next_offset = usize::from(entry.next_offset);
264        if next_offset < size_of::<ipt_entry>() {
265            return Err(IpTableParseError::NextOffsetTooSmall { offset: next_offset });
266        }
267
268        Ok(Self { ip_info, target_offset, next_offset })
269    }
270}
271
272impl TryFrom<ip6t_entry> for EntryInfo {
273    type Error = IpTableParseError;
274
275    fn try_from(entry: ip6t_entry) -> Result<Self, Self::Error> {
276        let ip_info = IpInfo::try_from(entry.ipv6)?;
277
278        let target_offset = usize::from(entry.target_offset);
279        if target_offset < size_of::<ip6t_entry>() {
280            return Err(IpTableParseError::TargetOffsetTooSmall { offset: target_offset });
281        }
282
283        let next_offset = usize::from(entry.next_offset);
284        if next_offset < size_of::<ip6t_entry>() {
285            return Err(IpTableParseError::NextOffsetTooSmall { offset: next_offset });
286        }
287
288        Ok(Self { ip_info, target_offset, next_offset })
289    }
290}
291
292#[derive(Clone, Debug)]
293pub struct IpInfo {
294    pub src_subnet: Option<fnet::Subnet>,
295    pub dst_subnet: Option<fnet::Subnet>,
296    pub in_interface: Option<String>,
297    pub out_interface: Option<String>,
298    pub inverse_flags: IptIpInverseFlags,
299    pub protocol: c_uint,
300    pub flags: IptIpFlags,
301
302    // Only for IPv6
303    pub tos: c_uchar,
304}
305
306impl TryFrom<ipt_ip> for IpInfo {
307    type Error = IpTableParseError;
308
309    fn try_from(ip: ipt_ip) -> Result<Self, Self::Error> {
310        let ipt_ip {
311            src,
312            dst,
313            smsk,
314            dmsk,
315            iniface,
316            outiface,
317            iniface_mask,
318            outiface_mask,
319            proto,
320            flags,
321            invflags,
322        } = ip;
323        let src_subnet =
324            ipv4_to_subnet(src, smsk).map_err(IpTableParseError::IpAddressConversion)?;
325        let dst_subnet =
326            ipv4_to_subnet(dst, dmsk).map_err(IpTableParseError::IpAddressConversion)?;
327
328        let in_interface = if iniface_mask == [0u8; 16] {
329            None
330        } else {
331            Some(ascii_to_string(&iniface).map_err(IpTableParseError::AsciiConversion)?)
332        };
333        let out_interface = if outiface_mask == [0u8; 16] {
334            None
335        } else {
336            Some(ascii_to_string(&outiface).map_err(IpTableParseError::AsciiConversion)?)
337        };
338
339        let flags_v4 = IptIpFlagsV4::from_bits(flags.into())
340            .ok_or(IpTableParseError::InvalidIpFlags { flags })?;
341        let inverse_flags = IptIpInverseFlags::from_bits(invflags.into())
342            .ok_or(IpTableParseError::InvalidIpInverseFlags { flags: invflags })?;
343
344        Ok(Self {
345            src_subnet,
346            dst_subnet,
347            in_interface,
348            out_interface,
349            inverse_flags,
350            protocol: proto.into(),
351            flags: IptIpFlags::V4(flags_v4),
352            // Unused in IPv4
353            tos: 0,
354        })
355    }
356}
357
358impl TryFrom<ip6t_ip6> for IpInfo {
359    type Error = IpTableParseError;
360
361    fn try_from(ip: ip6t_ip6) -> Result<Self, Self::Error> {
362        let ip6t_ip6 {
363            src,
364            dst,
365            smsk,
366            dmsk,
367            iniface,
368            outiface,
369            iniface_mask,
370            outiface_mask,
371            proto,
372            tos,
373            flags,
374            invflags,
375            __bindgen_padding_0,
376        } = ip;
377        let src_subnet =
378            ipv6_to_subnet(src, smsk).map_err(IpTableParseError::IpAddressConversion)?;
379        let dst_subnet =
380            ipv6_to_subnet(dst, dmsk).map_err(IpTableParseError::IpAddressConversion)?;
381
382        let in_interface = if iniface_mask == [0u8; 16] {
383            None
384        } else {
385            Some(ascii_to_string(&iniface).map_err(IpTableParseError::AsciiConversion)?)
386        };
387        let out_interface = if outiface_mask == [0u8; 16] {
388            None
389        } else {
390            Some(ascii_to_string(&outiface).map_err(IpTableParseError::AsciiConversion)?)
391        };
392
393        let flags_v6 = IptIpFlagsV6::from_bits(flags.into())
394            .ok_or(IpTableParseError::InvalidIpFlags { flags })?;
395        let inverse_flags = IptIpInverseFlags::from_bits(invflags.into())
396            .ok_or(IpTableParseError::InvalidIpInverseFlags { flags: invflags })?;
397
398        Ok(Self {
399            src_subnet,
400            dst_subnet,
401            in_interface,
402            out_interface,
403            inverse_flags,
404            protocol: proto.into(),
405            flags: IptIpFlags::V6(flags_v6),
406            tos,
407        })
408    }
409}
410
411impl IpInfo {
412    // For IPv6, whether to match protocol is configured via a flag.
413    fn should_match_protocol(&self) -> bool {
414        match self.flags {
415            IptIpFlags::V4(_) => true,
416            IptIpFlags::V6(flags) => flags.contains(IptIpFlagsV6::PROTOCOL),
417        }
418    }
419}
420
421/// An "Entry" is either:
422///
423/// 1. Start of a new iptables chain
424/// 2. A rule on the chain
425/// 3. The policy of the chain
426/// 4. End of input
427#[derive(Debug)]
428pub struct Entry {
429    /// bytes since the first entry, referred to by JUMP targets.
430    pub byte_pos: usize,
431
432    pub entry_info: EntryInfo,
433    pub matchers: Vec<Matcher>,
434    pub target: Target,
435}
436
437#[derive(Debug)]
438pub enum Matcher {
439    Unknown,
440    Tcp(xt_tcp),
441    Udp(xt_udp),
442    Bpf { path: BString },
443}
444
445#[derive(Debug)]
446pub enum Target {
447    Unknown { name: String, bytes: Vec<u8> },
448
449    // Parsed from `xt_standard_target`, which contains a numerical verdict.
450    //
451    // A 0 or positive verdict is a JUMP to another chain or rule, and a negative verdict
452    // is one of the builtin targets like ACCEPT, DROP or RETURN.
453    Standard(c_int),
454
455    // Parsed from `xt_error_target`, which contains a string.
456    //
457    // This misleading variant name does not indicate an error in parsing/translation, but rather
458    // the start of a chain or the end of input. The inner string is either the name of a chain
459    // that the following rule-specifications belong to, or "ERROR" if it is the last entry in the
460    // list of entries. Note that "ERROR" does not necessarily indicate the last entry, as a chain
461    // can be named "ERROR".
462    Error(String),
463
464    // Parsed from `nf_nat_ipv4_multi_range_compat` for IPv4, and `nf_nat_range` for IPv6.
465    //
466    // The original Linux structs are also used for the DNAT target, and contains information about
467    // IP addresses which is ignored for REDIRECT.
468    Redirect(NfNatRange),
469
470    // Parsed from `xt_tproxy_target_info` for IPv4, and `xt_tproxy_target_info_v1` for IPv6.
471    Tproxy(TproxyInfo),
472
473    // Parsed from `xt_mark_tginfo2`.
474    Mark { mask: u32, mark: u32 },
475
476    // Proceed to the next rule in the chain.
477    Next,
478}
479
480#[derive(Debug)]
481pub struct NfNatRange {
482    flags: NfNatRangeFlags,
483    start: u16,
484    end: u16,
485}
486
487#[derive(Debug)]
488pub struct TproxyInfo {
489    address: Option<fnet::IpAddress>,
490    port: Option<NonZeroU16>,
491}
492
493// `xt_standard_target` without the `target` field.
494//
495// `target` of type `xt_entry_target` is parsed first to determine the target's variant.
496#[repr(C)]
497#[derive(IntoBytes, Debug, Default, KnownLayout, FromBytes, Immutable)]
498pub struct VerdictWithPadding {
499    pub verdict: c_int,
500    pub _padding: [u8; 4usize],
501}
502
503// `xt_error_target` without the `target` field.
504//
505// `target` of type `xt_entry_target` is parsed first to determine the target's variant.
506#[repr(C)]
507#[derive(IntoBytes, Debug, Default, KnownLayout, FromBytes, Immutable)]
508pub struct ErrorNameWithPadding {
509    pub errorname: [c_char; 30usize],
510    pub _padding: [u8; 2usize],
511}
512
513#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
514pub enum Ip {
515    V4,
516    V6,
517}
518
519#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
520pub enum TableId {
521    Filter,
522    Mangle,
523    Nat,
524    Raw,
525}
526pub const NUM_TABLES: usize = TableId::Raw as usize + 1;
527
528impl TryFrom<&[c_char; starnix_uapi::XT_TABLE_MAXNAMELEN as usize]> for TableId {
529    type Error = IpTableParseError;
530
531    fn try_from(
532        value: &[c_char; starnix_uapi::XT_TABLE_MAXNAMELEN as usize],
533    ) -> Result<Self, Self::Error> {
534        let name = ascii_to_string(value).map_err(IpTableParseError::AsciiConversion)?;
535        if name == TABLE_FILTER {
536            return Ok(TableId::Filter);
537        } else if name == TABLE_MANGLE {
538            return Ok(TableId::Mangle);
539        } else if name == TABLE_NAT {
540            return Ok(TableId::Nat);
541        } else if name == TABLE_RAW {
542            return Ok(TableId::Raw);
543        }
544        Err(IpTableParseError::UnrecognizedTableName { table_name: name })
545    }
546}
547
548impl TableId {
549    fn to_str(&self) -> &'static str {
550        match self {
551            TableId::Filter => TABLE_FILTER,
552            TableId::Mangle => TABLE_MANGLE,
553            TableId::Nat => TABLE_NAT,
554            TableId::Raw => TABLE_RAW,
555        }
556    }
557}
558
559impl From<TableId> for usize {
560    fn from(value: TableId) -> Self {
561        value as usize
562    }
563}
564
565pub trait IptReplaceContext {
566    fn resolve_ebpf_socket_filter(
567        &mut self,
568        path: &BString,
569    ) -> Result<febpf::ProgramId, IpTableParseError>;
570}
571
572/// A parser for both `ipt_replace` and `ip6t_replace`, and its subsequent entries.
573#[derive(Debug)]
574pub struct IptReplaceParser {
575    /// Determines which Linux structures the parser expects.
576    protocol: Ip,
577
578    /// Table metadata passed through `ipt_replace` or `ip6t_replace`.
579    pub replace_info: ReplaceInfo,
580
581    // Linux bytes to parse.
582    //
583    // General layout is an `ipt_replace` followed by N "entries", where each "entry" is
584    // an `ipt_entry` and a `xt_*_target` with 0 or more "matchers" in between.
585    //
586    // In this IPv4 example, each row after the first is an entry:
587    //
588    //        [ ipt_replace ]
589    //   0:   [ ipt_entry ][ xt_error_target ]
590    //   1:   [ ipt_entry ][ xt_entry_match ][ xt_tcp ] ... [ xt_standard_target ]
591    //   2:   [ ipt_entry ][ xt_error_target ]
592    //        ...
593    //   N-1: [ ipt_entry ][ xt_error_target ]
594    //
595    // The main difference for IPv6 is that `ipt_entry` is replaced by `ip6t_entry`, which contains
596    // 128-bit IP addresses.
597    bytes: Vec<u8>,
598
599    /// Current parse position.
600    parse_pos: usize,
601
602    /// Keeps track of byte offsets of entries parsed so far. Used to check for errors.
603    entry_offsets: HashSet<usize>,
604}
605
606impl IptReplaceParser {
607    /// Initialize a new parser and tries to parse an `ipt_replace` struct from the buffer.
608    /// The rest of the buffer is left unparsed.
609    fn new_ipv4(bytes: Vec<u8>) -> Result<Self, IpTableParseError> {
610        let (ipt_replace, _) = ipt_replace::read_from_prefix(&bytes)
611            .map_err(|_| IpTableParseError::BufferTooSmallForMetadata { size: bytes.len() })?;
612        let replace_info = ReplaceInfo::try_from(ipt_replace)?;
613
614        if replace_info.size != bytes.len() - IPT_REPLACE_SIZE {
615            return Err(IpTableParseError::SizeMismatch {
616                specified_size: replace_info.size,
617                entries_size: bytes.len() - IPT_REPLACE_SIZE,
618            });
619        }
620
621        Ok(Self {
622            protocol: Ip::V4,
623            replace_info,
624            bytes,
625            parse_pos: IPT_REPLACE_SIZE,
626            entry_offsets: HashSet::new(),
627        })
628    }
629
630    /// Initialize a new parser and tries to parse an `ip6t_replace` struct from the buffer.
631    /// The rest of the buffer is left unparsed.
632    fn new_ipv6(bytes: Vec<u8>) -> Result<Self, IpTableParseError> {
633        let (ip6t_replace, _) = ip6t_replace::read_from_prefix(&bytes)
634            .map_err(|_| IpTableParseError::BufferTooSmallForMetadata { size: bytes.len() })?;
635        let replace_info = ReplaceInfo::try_from(ip6t_replace)?;
636
637        if replace_info.size != bytes.len() - IP6T_REPLACE_SIZE {
638            return Err(IpTableParseError::SizeMismatch {
639                specified_size: replace_info.size,
640                entries_size: bytes.len() - IP6T_REPLACE_SIZE,
641            });
642        }
643
644        Ok(Self {
645            protocol: Ip::V6,
646            replace_info,
647            bytes,
648            parse_pos: IP6T_REPLACE_SIZE,
649            entry_offsets: HashSet::new(),
650        })
651    }
652
653    fn finished(&self) -> bool {
654        self.parse_pos >= self.bytes.len()
655    }
656
657    pub fn entries_bytes(&self) -> &[u8] {
658        match self.protocol {
659            Ip::V4 => &self.bytes[IPT_REPLACE_SIZE..],
660            Ip::V6 => &self.bytes[IP6T_REPLACE_SIZE..],
661        }
662    }
663
664    fn get_domain(&self) -> fnet_filter_ext::Domain {
665        match self.protocol {
666            Ip::V4 => fnet_filter_ext::Domain::Ipv4,
667            Ip::V6 => fnet_filter_ext::Domain::Ipv6,
668        }
669    }
670
671    pub fn get_namespace_id(&self) -> fnet_filter_ext::NamespaceId {
672        match self.protocol {
673            Ip::V4 => fnet_filter_ext::NamespaceId(format!(
674                "ipv4-{}",
675                self.replace_info.table_id.to_str()
676            )),
677            Ip::V6 => fnet_filter_ext::NamespaceId(format!(
678                "ipv6-{}",
679                self.replace_info.table_id.to_str()
680            )),
681        }
682    }
683
684    pub fn table_id(&self) -> TableId {
685        self.replace_info.table_id
686    }
687
688    pub fn get_namespace(&self) -> fnet_filter_ext::Namespace {
689        fnet_filter_ext::Namespace { id: self.get_namespace_id(), domain: self.get_domain() }
690    }
691
692    fn get_custom_routine_type(&self) -> fnet_filter_ext::RoutineType {
693        match self.table_id() {
694            TableId::Nat => fnet_filter_ext::RoutineType::Nat(None),
695            TableId::Filter | TableId::Mangle | TableId::Raw => {
696                fnet_filter_ext::RoutineType::Ip(None)
697            }
698        }
699    }
700
701    /// Returns whether `offset` points to a valid entry. Parser can only know this after reading
702    /// all entries in `bytes`. Panics if the parser has not finished.
703    pub fn is_valid_offset(&self, offset: usize) -> bool {
704        assert!(self.finished());
705        self.entry_offsets.contains(&offset)
706    }
707
708    fn bytes_since_first_entry(&self) -> usize {
709        match self.protocol {
710            Ip::V4 => self
711                .parse_pos
712                .checked_sub(IPT_REPLACE_SIZE)
713                .expect("parse_pos is always larger or equal to size of ipt_replace"),
714            Ip::V6 => self
715                .parse_pos
716                .checked_sub(IP6T_REPLACE_SIZE)
717                .expect("parse_pos is always larger or equal to size of ip6t_replace"),
718        }
719    }
720
721    fn get_next_bytes(&self, offset: usize) -> Option<&[u8]> {
722        let new_pos = self.parse_pos + offset;
723        if new_pos > self.bytes.len() { None } else { Some(&self.bytes[self.parse_pos..new_pos]) }
724    }
725
726    // Parse `bytes` starting from `parse_pos` as type T, without advancing `parse_pos`.
727    // Used in cases where part of a structure must be parsed first, before determining how to parse
728    // the rest of the structure.
729
730    fn view_next_bytes_as<T: FromBytes>(&self) -> Result<T, IpTableParseError> {
731        let bytes = self.get_next_bytes(size_of::<T>()).ok_or_else(|| {
732            IpTableParseError::ParseEndOfBuffer {
733                type_name: type_name::<T>(),
734                position: self.parse_pos,
735            }
736        })?;
737        let obj = T::read_from_bytes(bytes).expect("read_from slice of exact size is successful");
738        Ok(obj)
739    }
740
741    // Add `offset` to `parse_pos`. Should be used after `view_next_bytes_as`.
742    fn advance_parse_pos(&mut self, offset: usize) -> Result<(), IpTableParseError> {
743        if self.parse_pos + offset > self.bytes.len() {
744            return Err(IpTableParseError::AdvanceEndOfBuffer { offset, position: self.parse_pos });
745        }
746        self.parse_pos += offset;
747        Ok(())
748    }
749
750    // Parse `bytes` starting from `parse_pos` as type T, and advance `parse_pos`.
751    fn parse_next_bytes_as<T: FromBytes>(&mut self) -> Result<T, IpTableParseError> {
752        let obj = self.view_next_bytes_as::<T>()?;
753        self.advance_parse_pos(size_of::<T>())?;
754        Ok(obj)
755    }
756
757    /// Parse the next Entry.
758    ///
759    /// An Entry is an `ipt_entry` (or `ip6t_entry` for IPv6), followed by 0 or more Matchers, and
760    /// finally a Target.
761    /// This method must advance `parse_pos`, so callers can assume that repeatedly calling this
762    /// method will eventually terminate (i.e. `finished()` returns true) if no error is returned.
763    fn parse_entry(&mut self) -> Result<Entry, IpTableParseError> {
764        let byte_pos = self.bytes_since_first_entry();
765
766        let entry_info = match self.protocol {
767            Ip::V4 => EntryInfo::try_from(self.parse_next_bytes_as::<ipt_entry>()?)?,
768            Ip::V6 => EntryInfo::try_from(self.parse_next_bytes_as::<ip6t_entry>()?)?,
769        };
770
771        let target_pos = byte_pos + entry_info.target_offset;
772        let next_pos = byte_pos + entry_info.next_offset;
773
774        let mut matchers = Vec::new();
775
776        // Each entry has 0 or more matchers.
777        while self.bytes_since_first_entry() < target_pos {
778            matchers.push(self.parse_matcher()?);
779        }
780
781        // Check if matchers extend beyond the `target_offset`.
782        if self.bytes_since_first_entry() != target_pos {
783            return Err(IpTableParseError::InvalidTargetOffset {
784                offset: entry_info.target_offset,
785            });
786        }
787
788        // Each entry has 1 target.
789        let target = self.parse_target()?;
790
791        // Check if matchers and target extend beyond the `next_offset`.
792        if self.bytes_since_first_entry() != next_pos {
793            return Err(IpTableParseError::InvalidNextOffset { offset: entry_info.next_offset });
794        }
795
796        assert!(self.bytes_since_first_entry() > byte_pos, "parse_pos must advance");
797
798        assert!(self.entry_offsets.insert(byte_pos));
799        Ok(Entry { byte_pos, entry_info, matchers, target })
800    }
801
802    /// Parses next bytes as a `xt_entry_match` struct and a specified matcher struct.
803    fn parse_matcher(&mut self) -> Result<Matcher, IpTableParseError> {
804        let match_info = self.parse_next_bytes_as::<xt_entry_match>()?;
805
806        let match_size = usize::from(match_info.match_size);
807        if match_size < size_of::<xt_entry_match>() {
808            return Err(IpTableParseError::MatchSizeTooSmall { size: match_size });
809        }
810        let remaining_size = match_size - size_of::<xt_entry_match>();
811        let name = ascii_to_string(&match_info.name).map_err(IpTableParseError::AsciiConversion)?;
812        let revision = match_info.revision;
813
814        let matcher = match (name.as_str(), revision) {
815            ("tcp", 0) => {
816                if remaining_size < size_of::<xt_tcp>() {
817                    return Err(IpTableParseError::MatchSizeMismatch {
818                        size: match_size,
819                        match_name: "tcp",
820                    });
821                }
822                let tcp = self.view_next_bytes_as::<xt_tcp>()?;
823                Matcher::Tcp(tcp)
824            }
825
826            ("udp", 0) => {
827                if remaining_size < size_of::<xt_udp>() {
828                    return Err(IpTableParseError::MatchSizeMismatch {
829                        size: match_size,
830                        match_name: "udp",
831                    });
832                }
833                let udp = self.view_next_bytes_as::<xt_udp>()?;
834                Matcher::Udp(udp)
835            }
836
837            ("bpf", 1) => {
838                if remaining_size < size_of::<xt_bpf_info_v1>() {
839                    return Err(IpTableParseError::MatchSizeMismatch {
840                        size: match_size,
841                        match_name: "bpf",
842                    });
843                }
844                let bpf = self.view_next_bytes_as::<xt_bpf_info_v1>()?;
845                if u32::from(bpf.mode) != starnix_uapi::xt_bpf_modes_XT_BPF_MODE_FD_PINNED {
846                    return Err(IpTableParseError::UnsupportedBpfMatcherMode { mode: bpf.mode });
847                }
848
849                // SAFETY: reading union variant.
850                let cpath = unsafe { &bpf.__bindgen_anon_1.path };
851                let path = CStr::from_bytes_until_nul(cpath.as_bytes())
852                    .map_err(|_| IpTableParseError::NoNulInEbpfProgramPath)?;
853                let path = BString::from(path.to_bytes());
854
855                Matcher::Bpf { path }
856            }
857
858            (matcher_name, revision) => {
859                track_stub!(
860                    TODO("https://fxbug.dev/448203710"),
861                    format!("ignored matcher {matcher_name}, revision {revision}").as_str()
862                );
863                Matcher::Unknown
864            }
865        };
866
867        // Advance by `remaining_size` to account for padding and unsupported match extensions.
868        self.advance_parse_pos(remaining_size)?;
869        Ok(matcher)
870    }
871
872    /// Parses next bytes as a `xt_entry_target` struct and a specified target struct.
873    fn parse_target(&mut self) -> Result<Target, IpTableParseError> {
874        let target_info = self.parse_next_bytes_as::<xt_entry_target>()?;
875
876        let target_size = usize::from(target_info.target_size);
877        if target_size < size_of::<xt_entry_target>() {
878            return Err(IpTableParseError::TargetSizeTooSmall { size: target_size });
879        }
880        let remaining_size = target_size - size_of::<xt_entry_target>();
881
882        let target_name =
883            ascii_to_string(&target_info.name).map_err(IpTableParseError::AsciiConversion)?;
884        let target = match (target_name.as_str(), target_info.revision) {
885            (TARGET_STANDARD, 0) => {
886                if remaining_size < size_of::<VerdictWithPadding>() {
887                    return Err(IpTableParseError::TargetSizeMismatch {
888                        size: remaining_size,
889                        target_name: "standard",
890                    });
891                }
892                let standard_target = self.view_next_bytes_as::<VerdictWithPadding>()?;
893
894                let next_offset = self.bytes_since_first_entry() + remaining_size;
895                if standard_target.verdict.try_into() == Ok(next_offset) {
896                    Target::Next
897                } else {
898                    Target::Standard(standard_target.verdict)
899                }
900            }
901
902            (TARGET_ERROR, 0) => {
903                if remaining_size < size_of::<ErrorNameWithPadding>() {
904                    return Err(IpTableParseError::TargetSizeMismatch {
905                        size: remaining_size,
906                        target_name: "error",
907                    });
908                }
909                let error_target = self.view_next_bytes_as::<ErrorNameWithPadding>()?;
910                let errorname = ascii_to_string(&error_target.errorname)
911                    .map_err(IpTableParseError::AsciiConversion)?;
912                Target::Error(errorname)
913            }
914
915            (TARGET_REDIRECT, 0) => self.view_as_redirect_target(remaining_size)?,
916
917            (TARGET_TPROXY, 1) => self.view_as_tproxy_target(remaining_size)?,
918
919            (TARGET_MARK, 2) => self.view_as_mark_target(remaining_size)?,
920
921            (target_name, revision) => {
922                track_stub!(
923                    TODO("https://fxbug.dev/448203710"),
924                    format!("ignored unknown target {target_name} with revision: {revision}")
925                        .as_str()
926                );
927
928                let bytes = self
929                    .get_next_bytes(remaining_size)
930                    .ok_or(IpTableParseError::TargetSizeMismatch {
931                        size: remaining_size,
932                        target_name: "unknown",
933                    })?
934                    .to_vec();
935                Target::Unknown { name: target_name.to_owned(), bytes }
936            }
937        };
938
939        // Advance by `remaining_size` to account for padding and unsupported target extensions.
940        self.advance_parse_pos(remaining_size)?;
941        Ok(target)
942    }
943
944    fn view_as_redirect_target(&self, remaining_size: usize) -> Result<Target, IpTableParseError> {
945        match self.protocol {
946            Ip::V4 => {
947                if remaining_size < size_of::<nf_nat_ipv4_multi_range_compat>() {
948                    return Err(IpTableParseError::TargetSizeMismatch {
949                        size: remaining_size,
950                        target_name: TARGET_REDIRECT,
951                    });
952                }
953                let redirect_target =
954                    self.view_next_bytes_as::<nf_nat_ipv4_multi_range_compat>()?;
955
956                // There is always 1 range.
957                if redirect_target.rangesize != 1 {
958                    return Err(IpTableParseError::InvalidRedirectTargetRangeSize {
959                        range_size: redirect_target.rangesize,
960                    });
961                }
962                let range = redirect_target.range[0];
963                let flags = NfNatRangeFlags::from_bits(range.flags).ok_or({
964                    IpTableParseError::InvalidRedirectTargetFlags { flags: range.flags }
965                })?;
966
967                // SAFETY: This union object was created with FromBytes so it's safe to access any
968                // variant because all variants must be valid with all bit patterns. All variants of
969                // `nf_conntrack_man_proto` are `u16`.
970                #[allow(
971                    clippy::undocumented_unsafe_blocks,
972                    reason = "Force documented unsafe blocks in Starnix"
973                )]
974                Ok(Target::Redirect(NfNatRange {
975                    flags,
976                    start: u16::from_be(unsafe { range.min.all }),
977                    end: u16::from_be(unsafe { range.max.all }),
978                }))
979            }
980            Ip::V6 => {
981                if remaining_size < size_of::<nf_nat_range>() {
982                    return Err(IpTableParseError::TargetSizeMismatch {
983                        size: remaining_size,
984                        target_name: TARGET_REDIRECT,
985                    });
986                }
987                let range = self.view_next_bytes_as::<nf_nat_range>()?;
988                let flags = NfNatRangeFlags::from_bits(range.flags).ok_or({
989                    IpTableParseError::InvalidRedirectTargetFlags { flags: range.flags }
990                })?;
991
992                // SAFETY: This union object was created with FromBytes so it's safe to access any
993                // variant because all variants must be valid with all bit patterns. All variants of
994                // `nf_conntrack_man_proto` are `u16`.
995                #[allow(
996                    clippy::undocumented_unsafe_blocks,
997                    reason = "Force documented unsafe blocks in Starnix"
998                )]
999                Ok(Target::Redirect(NfNatRange {
1000                    flags,
1001                    start: u16::from_be(unsafe { range.min_proto.all }),
1002                    end: u16::from_be(unsafe { range.max_proto.all }),
1003                }))
1004            }
1005        }
1006    }
1007
1008    fn view_as_tproxy_target(&self, remaining_size: usize) -> Result<Target, IpTableParseError> {
1009        if remaining_size < size_of::<xt_tproxy_target_info_v1>() {
1010            return Err(IpTableParseError::TargetSizeMismatch {
1011                size: remaining_size,
1012                target_name: "tproxy",
1013            });
1014        }
1015        let tproxy_target = self.view_next_bytes_as::<xt_tproxy_target_info_v1>()?;
1016
1017        // SAFETY: This union object was created with FromBytes so it's safe to access any variant
1018        // because all variants must be valid with all bit patterns. `nf_inet_addr` is a IPv4 or
1019        // or IPv6 address, depending on the protocol of the table.
1020        let address = if unsafe { tproxy_target.laddr.all } != [0u32; 4] {
1021            #[allow(
1022                clippy::undocumented_unsafe_blocks,
1023                reason = "Force documented unsafe blocks in Starnix"
1024            )]
1025            Some(match self.protocol {
1026                Ip::V4 => ipv4_addr_to_ip_address(unsafe { tproxy_target.laddr.in_ }),
1027                Ip::V6 => ipv6_addr_to_ip_address(unsafe { tproxy_target.laddr.in6 }),
1028            })
1029        } else {
1030            None
1031        };
1032        let port = NonZeroU16::new(u16::from_be(tproxy_target.lport));
1033        Ok(Target::Tproxy(TproxyInfo { address, port }))
1034    }
1035
1036    fn view_as_mark_target(&self, remaining_size: usize) -> Result<Target, IpTableParseError> {
1037        if remaining_size < size_of::<xt_mark_tginfo2>() {
1038            return Err(IpTableParseError::TargetSizeMismatch {
1039                size: remaining_size,
1040                target_name: "mark",
1041            });
1042        }
1043
1044        let mark_target = self.view_next_bytes_as::<xt_mark_tginfo2>()?;
1045
1046        Ok(Target::Mark { mark: mark_target.mark, mask: mark_target.mask })
1047    }
1048}
1049
1050#[derive(Debug)]
1051pub struct IpTable {
1052    /// The parser used to translate Linux data into fuchsia.net.filter resources.
1053    /// Included here as we don't have the reverse translation implemented yet.
1054    /// TODO(b/307908515): Remove once we can recreate Linux structure from net filter resources.
1055    pub parser: IptReplaceParser,
1056
1057    /// `namespace`, `routines` and `rules` make up an IPTable's representation
1058    /// in fuchsia.net.filter's API, where Namespace stores metadata about the table
1059    /// like its name, Routine correspond to a chain on the table, and Rule is a rule
1060    /// on a chain. We can update the table state of the system by dropping the Namespace,
1061    /// then recreating the Namespace, Routines, and Rules in that order.
1062    pub namespace: fnet_filter_ext::Namespace,
1063    pub routines: Vec<fnet_filter_ext::Routine>,
1064    pub rules: Vec<fnet_filter_ext::Rule>,
1065}
1066
1067impl IpTable {
1068    pub fn from_ipt_replace(
1069        context: &mut impl IptReplaceContext,
1070        bytes: Vec<u8>,
1071    ) -> Result<Self, IpTableParseError> {
1072        Self::from_parser(context, IptReplaceParser::new_ipv4(bytes)?)
1073    }
1074
1075    pub fn from_ip6t_replace(
1076        context: &mut impl IptReplaceContext,
1077        bytes: Vec<u8>,
1078    ) -> Result<Self, IpTableParseError> {
1079        Self::from_parser(context, IptReplaceParser::new_ipv6(bytes)?)
1080    }
1081
1082    fn from_parser(
1083        context: &mut impl IptReplaceContext,
1084        mut parser: IptReplaceParser,
1085    ) -> Result<Self, IpTableParseError> {
1086        let mut entries = Vec::new();
1087
1088        // Step 1: Parse entries table bytes into `Entry`s.
1089        while !parser.finished() {
1090            entries.push(parser.parse_entry()?);
1091        }
1092
1093        if entries.len() != parser.replace_info.num_entries {
1094            return Err(IpTableParseError::NumEntriesMismatch {
1095                specified: parser.replace_info.num_entries,
1096                found: entries.len(),
1097            });
1098        }
1099
1100        // There must be at least 1 entry and the last entry must be an error target named "ERROR".
1101        Self::check_and_remove_last_entry(&mut entries)?;
1102
1103        // Step 2: Translate both installed and custom routines. Group remaining `Entry`s with their
1104        // respective routines.
1105        let mut installed_routines = InstalledRoutines::new(&parser)?;
1106        let mut custom_routines = Vec::new();
1107
1108        for entry in entries {
1109            if let Some(installed_routine) = installed_routines.is_installed(entry.byte_pos)? {
1110                // Entries on installed routines cannot define a new custom routine.
1111                if let Target::Error(error_name) = entry.target {
1112                    return Err(IpTableParseError::UnexpectedErrorTarget { error_name });
1113                }
1114
1115                installed_routine.entries.push(entry);
1116            } else if let Target::Error(chain_name) = &entry.target {
1117                if !entry.matchers.is_empty() {
1118                    return Err(IpTableParseError::ErrorEntryHasMatchers);
1119                }
1120
1121                custom_routines.push(CustomRoutine {
1122                    routine: fnet_filter_ext::Routine {
1123                        id: fnet_filter_ext::RoutineId {
1124                            namespace: parser.get_namespace_id(),
1125                            name: chain_name.clone(),
1126                        },
1127                        routine_type: parser.get_custom_routine_type(),
1128                    },
1129                    entries: Vec::new(),
1130                });
1131            } else {
1132                let Some(current_routine) = custom_routines.last_mut() else {
1133                    return Err(IpTableParseError::RuleBeforeFirstChain);
1134                };
1135
1136                current_routine.entries.push(entry);
1137            }
1138        }
1139
1140        // Collect installed routines and custom routines. Build a custom routine lookup table to
1141        // translate JUMP rules that refer to routines by the byte position of their first entry.
1142        // Only custom routines can be JUMPed to.
1143        let mut routines = installed_routines.routines();
1144        let mut custom_routine_map = HashMap::new();
1145
1146        for custom_routine in &custom_routines {
1147            if let Some(first_entry) = custom_routine.entries.first() {
1148                custom_routine_map
1149                    .insert(first_entry.byte_pos, custom_routine.routine.id.name.to_owned());
1150            } else {
1151                // All custom routines must have at least 1 rule.
1152                return Err(IpTableParseError::ChainHasNoPolicy {
1153                    chain_name: custom_routine.routine.id.name.to_owned(),
1154                });
1155            }
1156
1157            routines.push(custom_routine.routine.clone());
1158        }
1159
1160        // Step 3: Translate rule entries into `fnet_filter_ext::Rule`s.
1161        let mut rules = Vec::new();
1162
1163        for installed_routine in installed_routines.into_iter() {
1164            for (index, entry) in installed_routine.entries.into_iter().enumerate() {
1165                if let Some(rule) = entry.translate_into_rule(
1166                    context,
1167                    installed_routine.routine.id.clone(),
1168                    index,
1169                    &custom_routine_map,
1170                )? {
1171                    rules.push(rule);
1172                }
1173            }
1174        }
1175        for custom_routine in custom_routines {
1176            for (index, entry) in custom_routine.entries.into_iter().enumerate() {
1177                if let Some(rule) = entry.translate_into_rule(
1178                    context,
1179                    custom_routine.routine.id.clone(),
1180                    index,
1181                    &custom_routine_map,
1182                )? {
1183                    rules.push(rule);
1184                }
1185            }
1186        }
1187
1188        let namespace = parser.get_namespace();
1189        Ok(IpTable { parser, namespace, routines, rules })
1190    }
1191
1192    pub fn into_changes(self) -> impl Iterator<Item = fnet_filter_ext::Change> {
1193        [
1194            // Firstly, remove the existing table, along with all of its routines and rules.
1195            // We will call Commit with idempotent=true so that this would succeed even if
1196            // the table did not exist prior to this change.
1197            fnet_filter_ext::Change::Remove(fnet_filter_ext::ResourceId::Namespace(
1198                self.namespace.id.clone(),
1199            )),
1200            // Recreate the table.
1201            fnet_filter_ext::Change::Create(fnet_filter_ext::Resource::Namespace(self.namespace)),
1202        ]
1203        .into_iter()
1204        .chain(
1205            self.routines
1206                .into_iter()
1207                .map(fnet_filter_ext::Resource::Routine)
1208                .map(fnet_filter_ext::Change::Create),
1209        )
1210        .chain(
1211            self.rules
1212                .into_iter()
1213                .map(fnet_filter_ext::Resource::Rule)
1214                .map(fnet_filter_ext::Change::Create),
1215        )
1216    }
1217
1218    fn check_and_remove_last_entry(entries: &mut Vec<Entry>) -> Result<(), IpTableParseError> {
1219        let last_entry = entries.last().ok_or(IpTableParseError::NoTrailingErrorTarget)?;
1220        if !last_entry.matchers.is_empty() {
1221            return Err(IpTableParseError::ErrorEntryHasMatchers);
1222        }
1223        if let Target::Error(chain_name) = &last_entry.target {
1224            if chain_name.as_str() != TARGET_ERROR {
1225                return Err(IpTableParseError::NoTrailingErrorTarget);
1226            }
1227        } else {
1228            return Err(IpTableParseError::NoTrailingErrorTarget);
1229        }
1230        entries.truncate(entries.len() - 1);
1231        Ok(())
1232    }
1233}
1234
1235/// A user-defined routine and the rule-specifications that belong to it.
1236struct CustomRoutine {
1237    routine: fnet_filter_ext::Routine,
1238    entries: Vec<Entry>,
1239}
1240
1241/// A built-in installed routine, specified by the byte range of the entries that belong to it.
1242struct InstalledRoutine {
1243    routine: fnet_filter_ext::Routine,
1244    byte_range: RangeInclusive<usize>,
1245    entries: Vec<Entry>,
1246}
1247
1248struct InstalledRoutines {
1249    prerouting: Option<InstalledRoutine>,
1250    input: Option<InstalledRoutine>,
1251    forward: Option<InstalledRoutine>,
1252    output: Option<InstalledRoutine>,
1253    postrouting: Option<InstalledRoutine>,
1254}
1255
1256impl InstalledRoutines {
1257    fn new(parser: &IptReplaceParser) -> Result<Self, IpTableParseError> {
1258        match parser.table_id() {
1259            TableId::Nat => {
1260                if parser.replace_info.valid_hooks != NfIpHooks::NAT {
1261                    return Err(IpTableParseError::InvalidHooksForTable {
1262                        hooks: parser.replace_info.valid_hooks.bits(),
1263                        table_name: TABLE_NAT,
1264                    });
1265                }
1266                Self::new_nat(parser)
1267            }
1268            TableId::Mangle => {
1269                if parser.replace_info.valid_hooks != NfIpHooks::MANGLE {
1270                    return Err(IpTableParseError::InvalidHooksForTable {
1271                        hooks: parser.replace_info.valid_hooks.bits(),
1272                        table_name: TABLE_MANGLE,
1273                    });
1274                }
1275                Self::new_ip(parser, nf_ip_hook_priorities_NF_IP_PRI_MANGLE)
1276            }
1277            TableId::Filter => {
1278                if parser.replace_info.valid_hooks != NfIpHooks::FILTER {
1279                    return Err(IpTableParseError::InvalidHooksForTable {
1280                        hooks: parser.replace_info.valid_hooks.bits(),
1281                        table_name: TABLE_FILTER,
1282                    });
1283                }
1284                Self::new_ip(parser, nf_ip_hook_priorities_NF_IP_PRI_FILTER)
1285            }
1286            TableId::Raw => {
1287                if parser.replace_info.valid_hooks != NfIpHooks::RAW {
1288                    return Err(IpTableParseError::InvalidHooksForTable {
1289                        hooks: parser.replace_info.valid_hooks.bits(),
1290                        table_name: TABLE_RAW,
1291                    });
1292                }
1293                Self::new_ip(parser, nf_ip_hook_priorities_NF_IP_PRI_RAW)
1294            }
1295        }
1296    }
1297
1298    fn new_nat(parser: &IptReplaceParser) -> Result<Self, IpTableParseError> {
1299        let prerouting = Some(InstalledRoutine {
1300            routine: fnet_filter_ext::Routine {
1301                id: fnet_filter_ext::RoutineId {
1302                    namespace: parser.get_namespace_id(),
1303                    name: CHAIN_PREROUTING.to_string(),
1304                },
1305                routine_type: fnet_filter_ext::RoutineType::Nat(Some(
1306                    fnet_filter_ext::InstalledNatRoutine {
1307                        hook: fnet_filter_ext::NatHook::Ingress,
1308                        priority: nf_ip_hook_priorities_NF_IP_PRI_NAT_DST,
1309                    },
1310                )),
1311            },
1312            byte_range: Self::make_byte_range(parser, NF_IP_PRE_ROUTING)?,
1313            entries: Vec::new(),
1314        });
1315        let input = Some(InstalledRoutine {
1316            routine: fnet_filter_ext::Routine {
1317                id: fnet_filter_ext::RoutineId {
1318                    namespace: parser.get_namespace_id(),
1319                    name: CHAIN_INPUT.to_string(),
1320                },
1321                routine_type: fnet_filter_ext::RoutineType::Nat(Some(
1322                    fnet_filter_ext::InstalledNatRoutine {
1323                        hook: fnet_filter_ext::NatHook::LocalIngress,
1324                        priority: nf_ip_hook_priorities_NF_IP_PRI_NAT_SRC,
1325                    },
1326                )),
1327            },
1328            byte_range: Self::make_byte_range(parser, NF_IP_LOCAL_IN)?,
1329            entries: Vec::new(),
1330        });
1331        let forward = None;
1332        let output = Some(InstalledRoutine {
1333            routine: fnet_filter_ext::Routine {
1334                id: fnet_filter_ext::RoutineId {
1335                    namespace: parser.get_namespace_id(),
1336                    name: CHAIN_OUTPUT.to_string(),
1337                },
1338                routine_type: fnet_filter_ext::RoutineType::Nat(Some(
1339                    fnet_filter_ext::InstalledNatRoutine {
1340                        hook: fnet_filter_ext::NatHook::LocalEgress,
1341                        priority: nf_ip_hook_priorities_NF_IP_PRI_NAT_DST,
1342                    },
1343                )),
1344            },
1345            byte_range: Self::make_byte_range(parser, NF_IP_LOCAL_OUT)?,
1346            entries: Vec::new(),
1347        });
1348
1349        let postrouting = Some(InstalledRoutine {
1350            routine: fnet_filter_ext::Routine {
1351                id: fnet_filter_ext::RoutineId {
1352                    namespace: parser.get_namespace_id(),
1353                    name: CHAIN_POSTROUTING.to_string(),
1354                },
1355                routine_type: fnet_filter_ext::RoutineType::Nat(Some(
1356                    fnet_filter_ext::InstalledNatRoutine {
1357                        hook: fnet_filter_ext::NatHook::Egress,
1358                        priority: nf_ip_hook_priorities_NF_IP_PRI_NAT_SRC,
1359                    },
1360                )),
1361            },
1362            byte_range: Self::make_byte_range(parser, NF_IP_POST_ROUTING)?,
1363            entries: Vec::new(),
1364        });
1365
1366        Ok(Self { prerouting, input, forward, output, postrouting })
1367    }
1368
1369    fn new_ip(parser: &IptReplaceParser, priority: i32) -> Result<Self, IpTableParseError> {
1370        let prerouting = if parser.replace_info.valid_hooks.contains(NfIpHooks::PREROUTING) {
1371            Some(InstalledRoutine {
1372                routine: fnet_filter_ext::Routine {
1373                    id: fnet_filter_ext::RoutineId {
1374                        namespace: parser.get_namespace_id(),
1375                        name: CHAIN_PREROUTING.to_string(),
1376                    },
1377                    routine_type: fnet_filter_ext::RoutineType::Ip(Some(
1378                        fnet_filter_ext::InstalledIpRoutine {
1379                            hook: fnet_filter_ext::IpHook::Ingress,
1380                            priority,
1381                        },
1382                    )),
1383                },
1384                byte_range: Self::make_byte_range(parser, NF_IP_PRE_ROUTING)?,
1385                entries: Vec::new(),
1386            })
1387        } else {
1388            None
1389        };
1390        let input = if parser.replace_info.valid_hooks.contains(NfIpHooks::INPUT) {
1391            Some(InstalledRoutine {
1392                routine: fnet_filter_ext::Routine {
1393                    id: fnet_filter_ext::RoutineId {
1394                        namespace: parser.get_namespace_id(),
1395                        name: CHAIN_INPUT.to_string(),
1396                    },
1397                    routine_type: fnet_filter_ext::RoutineType::Ip(Some(
1398                        fnet_filter_ext::InstalledIpRoutine {
1399                            hook: fnet_filter_ext::IpHook::LocalIngress,
1400                            priority,
1401                        },
1402                    )),
1403                },
1404                byte_range: Self::make_byte_range(parser, NF_IP_LOCAL_IN)?,
1405                entries: Vec::new(),
1406            })
1407        } else {
1408            None
1409        };
1410        let forward = if parser.replace_info.valid_hooks.contains(NfIpHooks::FORWARD) {
1411            Some(InstalledRoutine {
1412                routine: fnet_filter_ext::Routine {
1413                    id: fnet_filter_ext::RoutineId {
1414                        namespace: parser.get_namespace_id(),
1415                        name: CHAIN_FORWARD.to_string(),
1416                    },
1417                    routine_type: fnet_filter_ext::RoutineType::Ip(Some(
1418                        fnet_filter_ext::InstalledIpRoutine {
1419                            hook: fnet_filter_ext::IpHook::Forwarding,
1420                            priority,
1421                        },
1422                    )),
1423                },
1424                byte_range: Self::make_byte_range(parser, NF_IP_FORWARD)?,
1425                entries: Vec::new(),
1426            })
1427        } else {
1428            None
1429        };
1430        let output = if parser.replace_info.valid_hooks.contains(NfIpHooks::OUTPUT) {
1431            Some(InstalledRoutine {
1432                routine: fnet_filter_ext::Routine {
1433                    id: fnet_filter_ext::RoutineId {
1434                        namespace: parser.get_namespace_id(),
1435                        name: CHAIN_OUTPUT.to_string(),
1436                    },
1437                    routine_type: fnet_filter_ext::RoutineType::Ip(Some(
1438                        fnet_filter_ext::InstalledIpRoutine {
1439                            hook: fnet_filter_ext::IpHook::LocalEgress,
1440                            priority,
1441                        },
1442                    )),
1443                },
1444                byte_range: Self::make_byte_range(parser, NF_IP_LOCAL_OUT)?,
1445                entries: Vec::new(),
1446            })
1447        } else {
1448            None
1449        };
1450        let postrouting = if parser.replace_info.valid_hooks.contains(NfIpHooks::POSTROUTING) {
1451            Some(InstalledRoutine {
1452                routine: fnet_filter_ext::Routine {
1453                    id: fnet_filter_ext::RoutineId {
1454                        namespace: parser.get_namespace_id(),
1455                        name: CHAIN_POSTROUTING.to_string(),
1456                    },
1457                    routine_type: fnet_filter_ext::RoutineType::Ip(Some(
1458                        fnet_filter_ext::InstalledIpRoutine {
1459                            hook: fnet_filter_ext::IpHook::Egress,
1460                            priority,
1461                        },
1462                    )),
1463                },
1464                byte_range: Self::make_byte_range(parser, NF_IP_POST_ROUTING)?,
1465                entries: Vec::new(),
1466            })
1467        } else {
1468            None
1469        };
1470
1471        Ok(Self { prerouting, input, forward, output, postrouting })
1472    }
1473
1474    /// Returns a new byte range for the i-th hook. `index` must be less than `NF_IP_NUMHOOKS`.
1475    fn make_byte_range(
1476        parser: &IptReplaceParser,
1477        index: u32,
1478    ) -> Result<RangeInclusive<usize>, IpTableParseError> {
1479        assert!(index < NF_IP_NUMHOOKS);
1480
1481        let start = parser.replace_info.hook_entry[index as usize] as usize;
1482        let end = parser.replace_info.underflow[index as usize] as usize;
1483
1484        // Both start and end must point to an Entry.
1485        if start > end || !parser.is_valid_offset(start) || !parser.is_valid_offset(end) {
1486            return Err(IpTableParseError::InvalidHookEntryOrUnderflow { index, start, end });
1487        }
1488
1489        Ok(start..=end)
1490    }
1491
1492    fn iter(&self) -> impl Iterator<Item = &InstalledRoutine> {
1493        [&self.prerouting, &self.input, &self.forward, &self.output, &self.postrouting]
1494            .into_iter()
1495            .filter_map(|installed_routine| installed_routine.as_ref())
1496    }
1497
1498    fn iter_mut(&mut self) -> impl Iterator<Item = &mut InstalledRoutine> {
1499        [
1500            &mut self.prerouting,
1501            &mut self.input,
1502            &mut self.forward,
1503            &mut self.output,
1504            &mut self.postrouting,
1505        ]
1506        .into_iter()
1507        .filter_map(|installed_routine| installed_routine.as_mut())
1508    }
1509
1510    fn into_iter(self) -> impl Iterator<Item = InstalledRoutine> {
1511        [self.prerouting, self.input, self.forward, self.output, self.postrouting]
1512            .into_iter()
1513            .filter_map(|installed_routine| installed_routine)
1514    }
1515
1516    /// Given an `offset`, determine whether it belongs in exactly 1 installed routine of the table.
1517    /// Returns a mutable reference of the installed routine.
1518    /// Errors if an offset is found to be part of multiple installed routines.
1519    fn is_installed(
1520        &mut self,
1521        offset: usize,
1522    ) -> Result<Option<&mut InstalledRoutine>, IpTableParseError> {
1523        self.iter_mut()
1524            .filter(|routine| routine.byte_range.contains(&offset))
1525            .at_most_one()
1526            .map_err(|_| IpTableParseError::HookRangesOverlap { offset })
1527    }
1528
1529    fn routines(&self) -> Vec<fnet_filter_ext::Routine> {
1530        self.iter().map(|installed_routine| installed_routine.routine.clone()).collect()
1531    }
1532}
1533
1534impl Entry {
1535    // Creates a `fnet_filter_ext::Matchers` object and populate it with IP matchers in `ip_info`,
1536    // and extension matchers like `xt_tcp`.
1537    // Returns None if any unsupported matchers are found.
1538    fn get_rule_matchers(
1539        &self,
1540        context: &mut impl IptReplaceContext,
1541    ) -> Result<Option<fnet_filter_ext::Matchers>, IpTableParseError> {
1542        let mut matchers = fnet_filter_ext::Matchers::default();
1543
1544        if self.populate_matchers_with_ipt_ip(&mut matchers)?.is_none() {
1545            return Ok(None);
1546        }
1547        if self.populate_matchers_with_match_extensions(context, &mut matchers)?.is_none() {
1548            return Ok(None);
1549        }
1550
1551        Ok(Some(matchers))
1552    }
1553
1554    // Creates a `fnet_filter_ext::Action` from `target`.
1555    // Returns None if target is unsupported.
1556    fn get_rule_action(
1557        &self,
1558        routine_map: &HashMap<usize, String>,
1559    ) -> Result<Option<fnet_filter_ext::Action>, IpTableParseError> {
1560        match self.target {
1561            Target::Unknown { name: _, bytes: _ } => Ok(None),
1562
1563            // Error targets should already be translated into `Routine`s.
1564            Target::Error(_) => unreachable!(),
1565
1566            Target::Standard(verdict) => Self::translate_standard_target(verdict, routine_map),
1567
1568            Target::Redirect(ref range) => {
1569                if !range.flags.contains(NfNatRangeFlags::PROTO_SPECIFIED) {
1570                    Ok(Some(fnet_filter_ext::Action::Redirect { dst_port: None }))
1571                } else {
1572                    let invalid_range_fn = || IpTableParseError::InvalidRedirectTargetRange {
1573                        start: range.start,
1574                        end: range.end,
1575                    };
1576
1577                    if range.start > range.end {
1578                        return Err(invalid_range_fn());
1579                    }
1580                    let start = NonZeroU16::new(range.start).ok_or_else(invalid_range_fn)?;
1581                    let end = NonZeroU16::new(range.end).ok_or_else(invalid_range_fn)?;
1582                    Ok(Some(fnet_filter_ext::Action::Redirect {
1583                        dst_port: Some(fnet_filter_ext::PortRange(start..=end)),
1584                    }))
1585                }
1586            }
1587
1588            Target::Tproxy(ref tproxy_info) => {
1589                let tproxy = match (tproxy_info.address, tproxy_info.port) {
1590                    (None, None) => return Err(IpTableParseError::InvalidTproxyZeroAddressAndPort),
1591                    (None, Some(port)) => fnet_filter_ext::TransparentProxy::LocalPort(port),
1592                    (Some(address), None) => fnet_filter_ext::TransparentProxy::LocalAddr(address),
1593                    (Some(address), Some(port)) => {
1594                        fnet_filter_ext::TransparentProxy::LocalAddrAndPort(address, port)
1595                    }
1596                };
1597
1598                Ok(Some(fnet_filter_ext::Action::TransparentProxy(tproxy)))
1599            }
1600
1601            Target::Mark { mark, mask } => Ok(Some(fnet_filter_ext::Action::Mark {
1602                domain: fnet::MarkDomain::Mark1,
1603                action: fnet_filter_ext::MarkAction::SetMark { mark, clearing_mask: mask },
1604            })),
1605
1606            Target::Next => Ok(Some(fnet_filter_ext::Action::None)),
1607        }
1608    }
1609
1610    fn populate_matchers_with_ipt_ip(
1611        &self,
1612        matchers: &mut fnet_filter_ext::Matchers,
1613    ) -> Result<Option<()>, IpTableParseError> {
1614        let ip_info = &self.entry_info.ip_info;
1615
1616        if let Some(subnet) = ip_info.src_subnet {
1617            let subnet = fnet_matchers_ext::Subnet::try_from(subnet)
1618                .map_err(|err| IpTableParseError::FidlConversion(err.into()))?;
1619            matchers.src_addr = Some(fnet_matchers_ext::Address {
1620                matcher: fnet_matchers_ext::AddressMatcherType::Subnet(subnet),
1621                invert: ip_info.inverse_flags.contains(IptIpInverseFlags::SOURCE_IP_ADDRESS),
1622            });
1623        }
1624
1625        if let Some(subnet) = ip_info.dst_subnet {
1626            let subnet = fnet_matchers_ext::Subnet::try_from(subnet)
1627                .map_err(|err| IpTableParseError::FidlConversion(err.into()))?;
1628            matchers.dst_addr = Some(fnet_matchers_ext::Address {
1629                matcher: fnet_matchers_ext::AddressMatcherType::Subnet(subnet),
1630                invert: ip_info.inverse_flags.contains(IptIpInverseFlags::DESTINATION_IP_ADDRESS),
1631            });
1632        }
1633
1634        if let Some(ref interface) = ip_info.in_interface {
1635            if ip_info.inverse_flags.contains(IptIpInverseFlags::INPUT_INTERFACE) {
1636                track_stub!(
1637                    TODO("https://fxbug.dev/448203710"),
1638                    "ignored rule-specification",
1639                    IptIpInverseFlags::INPUT_INTERFACE
1640                );
1641                return Ok(None);
1642            }
1643            matchers.in_interface = Some(fnet_matchers_ext::Interface::Name(interface.clone()))
1644        }
1645
1646        if let Some(ref interface) = ip_info.out_interface {
1647            if ip_info.inverse_flags.contains(IptIpInverseFlags::OUTPUT_INTERFACE) {
1648                track_stub!(
1649                    TODO("https://fxbug.dev/448203710"),
1650                    "ignored rule-specification",
1651                    IptIpInverseFlags::OUTPUT_INTERFACE
1652                );
1653                return Ok(None);
1654            }
1655            matchers.out_interface = Some(fnet_matchers_ext::Interface::Name(interface.clone()))
1656        }
1657
1658        if ip_info.should_match_protocol() {
1659            match ip_info.protocol {
1660                // matches both TCP and UDP, which is true by default.
1661                IPPROTO_IP => {}
1662
1663                IPPROTO_TCP => {
1664                    matchers.transport_protocol = Some(fnet_matchers_ext::TransportProtocol::Tcp {
1665                        // These fields are set later by `xt_tcp` match extension, if present.
1666                        src_port: None,
1667                        dst_port: None,
1668                    });
1669                }
1670
1671                IPPROTO_UDP => {
1672                    matchers.transport_protocol = Some(fnet_matchers_ext::TransportProtocol::Udp {
1673                        // These fields are set later by `xt_udp` match extension, if present.
1674                        src_port: None,
1675                        dst_port: None,
1676                    });
1677                }
1678
1679                IPPROTO_ICMP => {
1680                    matchers.transport_protocol = Some(fnet_matchers_ext::TransportProtocol::Icmp)
1681                }
1682
1683                IPPROTO_ICMPV6 => {
1684                    matchers.transport_protocol = Some(fnet_matchers_ext::TransportProtocol::Icmpv6)
1685                }
1686
1687                protocol => {
1688                    track_stub!(
1689                        TODO("https://fxbug.dev/448203710"),
1690                        "ignored rule-specification with protocol",
1691                        protocol
1692                    );
1693                    return Ok(None);
1694                }
1695            };
1696        }
1697
1698        Ok(Some(()))
1699    }
1700
1701    fn populate_matchers_with_match_extensions(
1702        &self,
1703        context: &mut impl IptReplaceContext,
1704        fnet_filter_matchers: &mut fnet_filter_ext::Matchers,
1705    ) -> Result<Option<()>, IpTableParseError> {
1706        for matcher in &self.matchers {
1707            match matcher {
1708                Matcher::Tcp(xt_tcp {
1709                    spts,
1710                    dpts,
1711                    invflags,
1712                    option: _,
1713                    flg_mask: _,
1714                    flg_cmp: _,
1715                }) => {
1716                    // TCP match extension is only valid if protocol is specified as TCP.
1717                    let Some(fnet_matchers_ext::TransportProtocol::Tcp { src_port, dst_port }) =
1718                        fnet_filter_matchers.transport_protocol.as_mut()
1719                    else {
1720                        return Err(IpTableParseError::MatchExtensionDoesNotMatchProtocol);
1721                    };
1722
1723                    let inverse_flags = XtTcpInverseFlags::from_bits((*invflags).into())
1724                        .ok_or(IpTableParseError::InvalidXtTcpInverseFlags { flags: *invflags })?;
1725
1726                    if src_port.is_some() || dst_port.is_some() {
1727                        return Err(IpTableParseError::MatchExtensionOverwrite);
1728                    }
1729                    src_port.replace(
1730                        fnet_matchers_ext::Port::new(
1731                            spts[0],
1732                            spts[1],
1733                            inverse_flags.contains(XtTcpInverseFlags::SOURCE_PORT),
1734                        )
1735                        .map_err(IpTableParseError::PortMatcher)?,
1736                    );
1737                    dst_port.replace(
1738                        fnet_matchers_ext::Port::new(
1739                            dpts[0],
1740                            dpts[1],
1741                            inverse_flags.contains(XtTcpInverseFlags::DESTINATION_PORT),
1742                        )
1743                        .map_err(IpTableParseError::PortMatcher)?,
1744                    );
1745                }
1746                Matcher::Udp(xt_udp { spts, dpts, invflags, __bindgen_padding_0 }) => {
1747                    // UDP match extension is only valid if protocol is specified as UDP.
1748                    let Some(fnet_matchers_ext::TransportProtocol::Udp { src_port, dst_port }) =
1749                        fnet_filter_matchers.transport_protocol.as_mut()
1750                    else {
1751                        return Err(IpTableParseError::MatchExtensionDoesNotMatchProtocol);
1752                    };
1753
1754                    let inverse_flags = XtUdpInverseFlags::from_bits((*invflags).into())
1755                        .ok_or(IpTableParseError::InvalidXtUdpInverseFlags { flags: *invflags })?;
1756
1757                    if src_port.is_some() || dst_port.is_some() {
1758                        return Err(IpTableParseError::MatchExtensionOverwrite);
1759                    }
1760                    src_port.replace(
1761                        fnet_matchers_ext::Port::new(
1762                            spts[0],
1763                            spts[1],
1764                            inverse_flags.contains(XtUdpInverseFlags::SOURCE_PORT),
1765                        )
1766                        .map_err(IpTableParseError::PortMatcher)?,
1767                    );
1768                    dst_port.replace(
1769                        fnet_matchers_ext::Port::new(
1770                            dpts[0],
1771                            dpts[1],
1772                            inverse_flags.contains(XtUdpInverseFlags::DESTINATION_PORT),
1773                        )
1774                        .map_err(IpTableParseError::PortMatcher)?,
1775                    );
1776                }
1777                Matcher::Bpf { path } => {
1778                    fnet_filter_matchers.ebpf_program =
1779                        Some(context.resolve_ebpf_socket_filter(path)?);
1780                }
1781                Matcher::Unknown => return Ok(None),
1782            }
1783        }
1784
1785        Ok(Some(()))
1786    }
1787
1788    fn translate_standard_target(
1789        verdict: i32,
1790        routine_map: &HashMap<usize, String>,
1791    ) -> Result<Option<fnet_filter_ext::Action>, IpTableParseError> {
1792        match verdict {
1793            // A 0 or positive verdict is a JUMP to another chain or rule, but jumping to another
1794            // rule is not supported by fuchsia.net.filter.
1795            verdict if verdict >= 0 => {
1796                let jump_target = usize::try_from(verdict).expect("positive i32 fits into usize");
1797                if let Some(routine_name) = routine_map.get(&jump_target) {
1798                    Ok(Some(fnet_filter_ext::Action::Jump(routine_name.clone())))
1799                } else {
1800                    Err(IpTableParseError::InvalidJumpTarget { jump_target })
1801                }
1802            }
1803
1804            // A negative verdict is one of the builtin targets.
1805            VERDICT_DROP => Ok(Some(fnet_filter_ext::Action::Drop)),
1806
1807            VERDICT_ACCEPT => Ok(Some(fnet_filter_ext::Action::Accept)),
1808
1809            VERDICT_QUEUE => {
1810                track_stub!(
1811                    TODO("https://fxbug.dev/448203710"),
1812                    "ignored unsupported QUEUE target"
1813                );
1814                Ok(None)
1815            }
1816
1817            VERDICT_RETURN => Ok(Some(fnet_filter_ext::Action::Return)),
1818
1819            verdict => Err(IpTableParseError::InvalidVerdict { verdict }),
1820        }
1821    }
1822
1823    fn translate_into_rule(
1824        self,
1825        context: &mut impl IptReplaceContext,
1826        routine_id: fnet_filter_ext::RoutineId,
1827        index: usize,
1828        routine_map: &HashMap<usize, String>,
1829    ) -> Result<Option<fnet_filter_ext::Rule>, IpTableParseError> {
1830        let index = u32::try_from(index).map_err(|_| IpTableParseError::TooManyRules)?;
1831
1832        let Some(matchers) = self.get_rule_matchers(context)? else {
1833            return Ok(None);
1834        };
1835
1836        let Some(action) = self.get_rule_action(routine_map)? else {
1837            return Ok(None);
1838        };
1839
1840        Ok(Some(fnet_filter_ext::Rule {
1841            id: fnet_filter_ext::RuleId { routine: routine_id, index },
1842            matchers,
1843            action,
1844        }))
1845    }
1846}
1847
1848// Errors if any character is not in ASCII range (0-127).
1849fn ascii_to_bytes(chars: &[c_char]) -> Result<Vec<u8>, AsciiConversionError> {
1850    if chars.iter().any(|&c| c as u8 > 127) {
1851        return Err(AsciiConversionError::NonAsciiChar);
1852    }
1853    Ok(chars.as_bytes().to_owned())
1854}
1855
1856fn ascii_to_string(chars: &[c_char]) -> Result<String, AsciiConversionError> {
1857    let bytes = ascii_to_bytes(chars)?;
1858    let c_str = CStr::from_bytes_until_nul(&bytes)
1859        .map_err(|_| AsciiConversionError::NulByteNotFound { chars: chars.to_vec() })?;
1860    // We've verified that all characters are in ASCII range, so the conversion should succeed.
1861    Ok(c_str.to_str().expect("failed CStr to Str conversion").to_owned())
1862}
1863
1864pub const fn string_to_ascii_buffer<const N: usize>(
1865    string: &str,
1866) -> Result<[c_char; N], AsciiConversionError> {
1867    let bytes = string.as_bytes();
1868    if bytes.len() > N - 1 {
1869        return Err(AsciiConversionError::BufferTooSmall {
1870            buffer_size: N,
1871            data_size: bytes.len() + 1,
1872        });
1873    }
1874    let mut chars = [0; N];
1875
1876    // Using `while` loop instead of `for` loop allows to make this function `const`.
1877    let mut i = 0;
1878    while i < bytes.len() {
1879        let byte = bytes[i];
1880        match byte {
1881            0 => return Err(AsciiConversionError::NulByteInString { pos: i }),
1882            byte if byte > 127 => return Err(AsciiConversionError::NonAsciiChar),
1883            _ => (),
1884        }
1885        chars[i] = byte as c_char;
1886        i += 1;
1887    }
1888    Ok(chars)
1889}
1890
1891// Assumes `addr` is big endian.
1892fn ipv4_addr_to_ip_address(addr: in_addr) -> fnet::IpAddress {
1893    fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: u32::from_be(addr.s_addr).to_be_bytes() })
1894}
1895
1896// Assumes `addr` is big endian.
1897fn ipv6_addr_to_ip_address(addr: in6_addr) -> fnet::IpAddress {
1898    // SAFETY: This union object was created with FromBytes so it's safe to access any variant
1899    // because all variants must be valid with all bit patterns. `in6_addr__bindgen_ty_1` is an IPv6
1900    // address, represented as sixteen 8-bit octets, or eight 16-bit segments, or four 32-bit words.
1901    // All variants have the same size and represent the same address.
1902    let addr_bytes = unsafe { addr.in6_u.u6_addr8 };
1903    fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr: addr_bytes })
1904}
1905
1906// Assumes `mask` is big endian.
1907fn ipv4_mask_to_prefix_len(mask: in_addr) -> Result<u8, IpAddressConversionError> {
1908    let mask = u32::from_be(mask.s_addr);
1909
1910    // Check that all 1's in the mask are before all 0's.
1911    // To do this, we can simply find if its 2-complement is a power of 2.
1912    if !mask.wrapping_neg().is_power_of_two() {
1913        return Err(IpAddressConversionError::IpV4SubnetMaskHasNonPrefixBits { mask });
1914    }
1915
1916    // Impossible to have more 1's in a `u32` than 255.
1917    Ok(mask.count_ones() as u8)
1918}
1919
1920// Assumes `mask` is in big endian order.
1921fn ipv6_mask_to_prefix_len(mask: [u8; 16]) -> Result<u8, IpAddressConversionError> {
1922    let mask = u128::from_be_bytes(mask);
1923
1924    // Check that all 1's in the mask are before all 0's.
1925    // To do this, we can simply find if its 2-complement is a power of 2.
1926    if !mask.wrapping_neg().is_power_of_two() {
1927        return Err(IpAddressConversionError::IpV6SubnetMaskHasNonPrefixBits { mask });
1928    }
1929
1930    // Impossible to have more 1's in a `u128` than 255.
1931    Ok(mask.count_ones() as u8)
1932}
1933
1934// Converts an IPv4 address and subnet mask to fuchsia.net.Subnet.
1935//
1936// Assumes `ipv4_addr` and `subnet_mask` are both big endian. Returns Ok(None) if subnet mask is 0.
1937// Errors if not all 1's in the subnet_mask are before all 0's.
1938pub fn ipv4_to_subnet(
1939    addr: in_addr,
1940    mask: in_addr,
1941) -> Result<Option<fnet::Subnet>, IpAddressConversionError> {
1942    if mask.s_addr == 0 {
1943        Ok(None)
1944    } else {
1945        Ok(Some(fnet::Subnet {
1946            addr: ipv4_addr_to_ip_address(addr),
1947            prefix_len: ipv4_mask_to_prefix_len(mask)?,
1948        }))
1949    }
1950}
1951
1952pub fn ipv6_to_subnet(
1953    addr: in6_addr,
1954    mask: in6_addr,
1955) -> Result<Option<fnet::Subnet>, IpAddressConversionError> {
1956    // SAFETY: This union object was created with FromBytes so it's safe to access any variant
1957    // because all variants must be valid with all bit patterns. `in6_addr__bindgen_ty_1` is an IPv6
1958    // address, represented as sixteen 8-bit octets, or eight 16-bit segments, or four 32-bit words.
1959    // All variants have the same size and represent the same address.
1960    let mask_bytes = unsafe { mask.in6_u.u6_addr8 };
1961
1962    if mask_bytes == [0u8; 16] {
1963        Ok(None)
1964    } else {
1965        Ok(Some(fnet::Subnet {
1966            addr: ipv6_addr_to_ip_address(addr),
1967            prefix_len: ipv6_mask_to_prefix_len(mask_bytes)?,
1968        }))
1969    }
1970}
1971
1972#[cfg(test)]
1973mod tests {
1974    use super::*;
1975    use itertools::Itertools;
1976    use net_declare::{fidl_ip, fidl_subnet};
1977    use starnix_uapi::{
1978        IP6T_F_PROTO, IPT_INV_SRCIP, XT_TCP_INV_DSTPT, c_char, in_addr, in6_addr__bindgen_ty_1,
1979        ipt_entry, ipt_ip, ipt_replace, xt_tcp, xt_udp,
1980    };
1981    use test_case::test_case;
1982    use {fidl_fuchsia_net as fnet, fidl_fuchsia_net_filter_ext as fnet_filter_ext};
1983
1984    const IPV4_SUBNET: fnet::Subnet = fidl_subnet!("192.0.2.0/24");
1985    const IPV4_ADDR: fnet::IpAddress = fidl_ip!("192.0.2.0");
1986    const IPV6_SUBNET: fnet::Subnet = fidl_subnet!("2001:db8::/32");
1987    const IPV6_ADDR: fnet::IpAddress = fidl_ip!("2001:db8::");
1988    const PORT: u16 = 2345u16.to_be();
1989    const PORT_RANGE_START: u16 = 2000u16.to_be();
1990    const PORT_RANGE_END: u16 = 3000u16.to_be();
1991    const NONZERO_PORT: NonZeroU16 = NonZeroU16::new(2345).unwrap();
1992    const NONZERO_PORT_RANGE: RangeInclusive<NonZeroU16> =
1993        RangeInclusive::new(NonZeroU16::new(2000).unwrap(), NonZeroU16::new(3000).unwrap());
1994    const MARK: u32 = 1;
1995    const MASK: u32 = 2;
1996
1997    const STANDARD_TARGET_SIZE: usize =
1998        size_of::<xt_entry_target>() + size_of::<VerdictWithPadding>();
1999
2000    fn extend_with_standard_verdict(bytes: &mut Vec<u8>, verdict: i32) {
2001        bytes.extend_from_slice(
2002            xt_entry_target { target_size: STANDARD_TARGET_SIZE as u16, ..Default::default() }
2003                .as_bytes(),
2004        );
2005        bytes.extend_from_slice(VerdictWithPadding { verdict, ..Default::default() }.as_bytes());
2006    }
2007
2008    fn extend_with_error_name(bytes: &mut Vec<u8>, error_name: &str) {
2009        bytes.extend_from_slice(
2010            xt_entry_target {
2011                target_size: 64,
2012                name: string_to_ascii_buffer(TARGET_ERROR).unwrap(),
2013                revision: 0,
2014            }
2015            .as_bytes(),
2016        );
2017        bytes.extend_from_slice(
2018            ErrorNameWithPadding {
2019                errorname: string_to_ascii_buffer(error_name).unwrap(),
2020                ..Default::default()
2021            }
2022            .as_bytes(),
2023        );
2024    }
2025
2026    fn extend_with_standard_target_ipv4_entry(bytes: &mut Vec<u8>, verdict: i32) {
2027        bytes.extend_from_slice(
2028            ipt_entry { target_offset: 112, next_offset: 152, ..Default::default() }.as_bytes(),
2029        );
2030        extend_with_standard_verdict(bytes, verdict);
2031    }
2032
2033    fn extend_with_standard_target_ipv6_entry(bytes: &mut Vec<u8>, verdict: i32) {
2034        bytes.extend_from_slice(
2035            ip6t_entry { target_offset: 168, next_offset: 208, ..Default::default() }.as_bytes(),
2036        );
2037        extend_with_standard_verdict(bytes, verdict);
2038    }
2039
2040    fn extend_with_error_target_ipv4_entry(bytes: &mut Vec<u8>, error_name: &str) {
2041        bytes.extend_from_slice(
2042            ipt_entry { target_offset: 112, next_offset: 176, ..Default::default() }.as_bytes(),
2043        );
2044        extend_with_error_name(bytes, error_name);
2045    }
2046
2047    fn extend_with_error_target_ipv6_entry(bytes: &mut Vec<u8>, error_name: &str) {
2048        bytes.extend_from_slice(
2049            ip6t_entry { target_offset: 168, next_offset: 232, ..Default::default() }.as_bytes(),
2050        );
2051        extend_with_error_name(bytes, error_name);
2052    }
2053
2054    fn filter_namespace_v4() -> fnet_filter_ext::Namespace {
2055        fnet_filter_ext::Namespace {
2056            id: fnet_filter_ext::NamespaceId("ipv4-filter".to_owned()),
2057            domain: fnet_filter_ext::Domain::Ipv4,
2058        }
2059    }
2060
2061    fn filter_namespace_v6() -> fnet_filter_ext::Namespace {
2062        fnet_filter_ext::Namespace {
2063            id: fnet_filter_ext::NamespaceId("ipv6-filter".to_owned()),
2064            domain: fnet_filter_ext::Domain::Ipv6,
2065        }
2066    }
2067
2068    fn filter_input_routine(namespace: &fnet_filter_ext::Namespace) -> fnet_filter_ext::Routine {
2069        fnet_filter_ext::Routine {
2070            id: fnet_filter_ext::RoutineId {
2071                namespace: namespace.id.clone(),
2072                name: CHAIN_INPUT.to_owned(),
2073            },
2074            routine_type: fnet_filter_ext::RoutineType::Ip(Some(
2075                fnet_filter_ext::InstalledIpRoutine {
2076                    hook: fnet_filter_ext::IpHook::LocalIngress,
2077                    priority: nf_ip_hook_priorities_NF_IP_PRI_FILTER,
2078                },
2079            )),
2080        }
2081    }
2082
2083    fn filter_forward_routine(namespace: &fnet_filter_ext::Namespace) -> fnet_filter_ext::Routine {
2084        fnet_filter_ext::Routine {
2085            id: fnet_filter_ext::RoutineId {
2086                namespace: namespace.id.clone(),
2087                name: CHAIN_FORWARD.to_owned(),
2088            },
2089            routine_type: fnet_filter_ext::RoutineType::Ip(Some(
2090                fnet_filter_ext::InstalledIpRoutine {
2091                    hook: fnet_filter_ext::IpHook::Forwarding,
2092                    priority: nf_ip_hook_priorities_NF_IP_PRI_FILTER,
2093                },
2094            )),
2095        }
2096    }
2097
2098    fn filter_output_routine(namespace: &fnet_filter_ext::Namespace) -> fnet_filter_ext::Routine {
2099        fnet_filter_ext::Routine {
2100            id: fnet_filter_ext::RoutineId {
2101                namespace: namespace.id.clone(),
2102                name: CHAIN_OUTPUT.to_owned(),
2103            },
2104            routine_type: fnet_filter_ext::RoutineType::Ip(Some(
2105                fnet_filter_ext::InstalledIpRoutine {
2106                    hook: fnet_filter_ext::IpHook::LocalEgress,
2107                    priority: nf_ip_hook_priorities_NF_IP_PRI_FILTER,
2108                },
2109            )),
2110        }
2111    }
2112
2113    fn filter_custom_routine(
2114        namespace: &fnet_filter_ext::Namespace,
2115        chain_name: &str,
2116    ) -> fnet_filter_ext::Routine {
2117        fnet_filter_ext::Routine {
2118            id: fnet_filter_ext::RoutineId {
2119                namespace: namespace.id.clone(),
2120                name: chain_name.to_owned(),
2121            },
2122            routine_type: fnet_filter_ext::RoutineType::Ip(None),
2123        }
2124    }
2125
2126    fn mangle_namespace_v4() -> fnet_filter_ext::Namespace {
2127        fnet_filter_ext::Namespace {
2128            id: fnet_filter_ext::NamespaceId("ipv4-mangle".to_owned()),
2129            domain: fnet_filter_ext::Domain::Ipv4,
2130        }
2131    }
2132
2133    fn mangle_namespace_v6() -> fnet_filter_ext::Namespace {
2134        fnet_filter_ext::Namespace {
2135            id: fnet_filter_ext::NamespaceId("ipv6-mangle".to_owned()),
2136            domain: fnet_filter_ext::Domain::Ipv6,
2137        }
2138    }
2139
2140    fn mangle_prerouting_routine(
2141        namespace: &fnet_filter_ext::Namespace,
2142    ) -> fnet_filter_ext::Routine {
2143        fnet_filter_ext::Routine {
2144            id: fnet_filter_ext::RoutineId {
2145                namespace: namespace.id.clone(),
2146                name: CHAIN_PREROUTING.to_owned(),
2147            },
2148            routine_type: fnet_filter_ext::RoutineType::Ip(Some(
2149                fnet_filter_ext::InstalledIpRoutine {
2150                    hook: fnet_filter_ext::IpHook::Ingress,
2151                    priority: nf_ip_hook_priorities_NF_IP_PRI_MANGLE,
2152                },
2153            )),
2154        }
2155    }
2156
2157    fn mangle_input_routine(namespace: &fnet_filter_ext::Namespace) -> fnet_filter_ext::Routine {
2158        fnet_filter_ext::Routine {
2159            id: fnet_filter_ext::RoutineId {
2160                namespace: namespace.id.clone(),
2161                name: CHAIN_INPUT.to_owned(),
2162            },
2163            routine_type: fnet_filter_ext::RoutineType::Ip(Some(
2164                fnet_filter_ext::InstalledIpRoutine {
2165                    hook: fnet_filter_ext::IpHook::LocalIngress,
2166                    priority: nf_ip_hook_priorities_NF_IP_PRI_MANGLE,
2167                },
2168            )),
2169        }
2170    }
2171
2172    fn mangle_forward_routine(namespace: &fnet_filter_ext::Namespace) -> fnet_filter_ext::Routine {
2173        fnet_filter_ext::Routine {
2174            id: fnet_filter_ext::RoutineId {
2175                namespace: namespace.id.clone(),
2176                name: CHAIN_FORWARD.to_owned(),
2177            },
2178            routine_type: fnet_filter_ext::RoutineType::Ip(Some(
2179                fnet_filter_ext::InstalledIpRoutine {
2180                    hook: fnet_filter_ext::IpHook::Forwarding,
2181                    priority: nf_ip_hook_priorities_NF_IP_PRI_MANGLE,
2182                },
2183            )),
2184        }
2185    }
2186
2187    fn mangle_output_routine(namespace: &fnet_filter_ext::Namespace) -> fnet_filter_ext::Routine {
2188        fnet_filter_ext::Routine {
2189            id: fnet_filter_ext::RoutineId {
2190                namespace: namespace.id.clone(),
2191                name: CHAIN_OUTPUT.to_owned(),
2192            },
2193            routine_type: fnet_filter_ext::RoutineType::Ip(Some(
2194                fnet_filter_ext::InstalledIpRoutine {
2195                    hook: fnet_filter_ext::IpHook::LocalEgress,
2196                    priority: nf_ip_hook_priorities_NF_IP_PRI_MANGLE,
2197                },
2198            )),
2199        }
2200    }
2201
2202    fn mangle_postrouting_routine(
2203        namespace: &fnet_filter_ext::Namespace,
2204    ) -> fnet_filter_ext::Routine {
2205        fnet_filter_ext::Routine {
2206            id: fnet_filter_ext::RoutineId {
2207                namespace: namespace.id.clone(),
2208                name: CHAIN_POSTROUTING.to_owned(),
2209            },
2210            routine_type: fnet_filter_ext::RoutineType::Ip(Some(
2211                fnet_filter_ext::InstalledIpRoutine {
2212                    hook: fnet_filter_ext::IpHook::Egress,
2213                    priority: nf_ip_hook_priorities_NF_IP_PRI_MANGLE,
2214                },
2215            )),
2216        }
2217    }
2218
2219    fn nat_namespace_v4() -> fnet_filter_ext::Namespace {
2220        fnet_filter_ext::Namespace {
2221            id: fnet_filter_ext::NamespaceId("ipv4-nat".to_owned()),
2222            domain: fnet_filter_ext::Domain::Ipv4,
2223        }
2224    }
2225
2226    fn nat_namespace_v6() -> fnet_filter_ext::Namespace {
2227        fnet_filter_ext::Namespace {
2228            id: fnet_filter_ext::NamespaceId("ipv6-nat".to_owned()),
2229            domain: fnet_filter_ext::Domain::Ipv6,
2230        }
2231    }
2232
2233    fn nat_prerouting_routine(namespace: &fnet_filter_ext::Namespace) -> fnet_filter_ext::Routine {
2234        fnet_filter_ext::Routine {
2235            id: fnet_filter_ext::RoutineId {
2236                namespace: namespace.id.clone(),
2237                name: CHAIN_PREROUTING.to_owned(),
2238            },
2239            routine_type: fnet_filter_ext::RoutineType::Nat(Some(
2240                fnet_filter_ext::InstalledNatRoutine {
2241                    hook: fnet_filter_ext::NatHook::Ingress,
2242                    priority: nf_ip_hook_priorities_NF_IP_PRI_NAT_DST,
2243                },
2244            )),
2245        }
2246    }
2247
2248    fn nat_input_routine(namespace: &fnet_filter_ext::Namespace) -> fnet_filter_ext::Routine {
2249        fnet_filter_ext::Routine {
2250            id: fnet_filter_ext::RoutineId {
2251                namespace: namespace.id.clone(),
2252                name: CHAIN_INPUT.to_owned(),
2253            },
2254            routine_type: fnet_filter_ext::RoutineType::Nat(Some(
2255                fnet_filter_ext::InstalledNatRoutine {
2256                    hook: fnet_filter_ext::NatHook::LocalIngress,
2257                    priority: nf_ip_hook_priorities_NF_IP_PRI_NAT_SRC,
2258                },
2259            )),
2260        }
2261    }
2262
2263    fn nat_output_routine(namespace: &fnet_filter_ext::Namespace) -> fnet_filter_ext::Routine {
2264        fnet_filter_ext::Routine {
2265            id: fnet_filter_ext::RoutineId {
2266                namespace: namespace.id.clone(),
2267                name: CHAIN_OUTPUT.to_owned(),
2268            },
2269            routine_type: fnet_filter_ext::RoutineType::Nat(Some(
2270                fnet_filter_ext::InstalledNatRoutine {
2271                    hook: fnet_filter_ext::NatHook::LocalEgress,
2272                    priority: nf_ip_hook_priorities_NF_IP_PRI_NAT_DST,
2273                },
2274            )),
2275        }
2276    }
2277
2278    fn nat_postrouting_routine(namespace: &fnet_filter_ext::Namespace) -> fnet_filter_ext::Routine {
2279        fnet_filter_ext::Routine {
2280            id: fnet_filter_ext::RoutineId {
2281                namespace: namespace.id.clone(),
2282                name: CHAIN_POSTROUTING.to_owned(),
2283            },
2284            routine_type: fnet_filter_ext::RoutineType::Nat(Some(
2285                fnet_filter_ext::InstalledNatRoutine {
2286                    hook: fnet_filter_ext::NatHook::Egress,
2287                    priority: nf_ip_hook_priorities_NF_IP_PRI_NAT_SRC,
2288                },
2289            )),
2290        }
2291    }
2292
2293    fn ipv4_subnet_addr() -> in_addr {
2294        let bytes = "192.0.2.0".parse::<std::net::Ipv4Addr>().unwrap().octets();
2295        in_addr { s_addr: u32::from_be_bytes(bytes).to_be() }
2296    }
2297
2298    fn ipv4_subnet_mask() -> in_addr {
2299        let bytes = "255.255.255.0".parse::<std::net::Ipv4Addr>().unwrap().octets();
2300        in_addr { s_addr: u32::from_be_bytes(bytes).to_be() }
2301    }
2302
2303    #[fuchsia::test]
2304    fn ipv4_subnet_test() {
2305        assert_eq!(ipv4_addr_to_ip_address(ipv4_subnet_addr()), IPV4_ADDR);
2306        assert_eq!(ipv4_to_subnet(ipv4_subnet_addr(), ipv4_subnet_mask()), Ok(Some(IPV4_SUBNET)));
2307    }
2308
2309    fn ipv6_subnet_addr() -> in6_addr {
2310        let bytes = "2001:db8::".parse::<std::net::Ipv6Addr>().unwrap().octets();
2311        in6_addr { in6_u: in6_addr__bindgen_ty_1 { u6_addr8: bytes } }
2312    }
2313
2314    fn ipv6_subnet_mask() -> in6_addr {
2315        let bytes = "ffff:ffff::".parse::<std::net::Ipv6Addr>().unwrap().octets();
2316        in6_addr { in6_u: in6_addr__bindgen_ty_1 { u6_addr8: bytes } }
2317    }
2318
2319    #[fuchsia::test]
2320    fn ipv6_subnet_test() {
2321        assert_eq!(ipv6_addr_to_ip_address(ipv6_subnet_addr()), IPV6_ADDR);
2322        assert_eq!(ipv6_to_subnet(ipv6_subnet_addr(), ipv6_subnet_mask()), Ok(Some(IPV6_SUBNET)));
2323    }
2324
2325    fn ipv4_table_with_ip_matchers() -> Vec<u8> {
2326        let mut entries_bytes = Vec::new();
2327
2328        // Start of INPUT built-in chain.
2329        let input_hook_entry = entries_bytes.len() as u32;
2330
2331        // Entry 0: drop TCP packets other than from `IPV4_SUBNET`.
2332        entries_bytes.extend_from_slice(
2333            ipt_entry {
2334                ip: ipt_ip {
2335                    src: ipv4_subnet_addr(),
2336                    smsk: ipv4_subnet_mask(),
2337                    invflags: IPT_INV_SRCIP as u8,
2338                    proto: IPPROTO_TCP as u16,
2339                    ..Default::default()
2340                },
2341                target_offset: 112,
2342                next_offset: 152,
2343                ..Default::default()
2344            }
2345            .as_bytes(),
2346        );
2347        extend_with_standard_verdict(&mut entries_bytes, VERDICT_DROP);
2348
2349        // Entry 1: accept UDP packets from `IPV4_SUBNET`.
2350        entries_bytes.extend_from_slice(
2351            ipt_entry {
2352                ip: ipt_ip {
2353                    src: ipv4_subnet_addr(),
2354                    smsk: ipv4_subnet_mask(),
2355                    proto: IPPROTO_UDP as u16,
2356                    ..Default::default()
2357                },
2358                target_offset: 112,
2359                next_offset: 152,
2360                ..Default::default()
2361            }
2362            .as_bytes(),
2363        );
2364        extend_with_standard_verdict(&mut entries_bytes, VERDICT_ACCEPT);
2365
2366        // Entry 2: drop all packets going to en0 interface.
2367        entries_bytes.extend_from_slice(
2368            ipt_entry {
2369                ip: ipt_ip {
2370                    iniface: string_to_ascii_buffer("en0").unwrap(),
2371                    iniface_mask: [255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
2372                    ..Default::default()
2373                },
2374                target_offset: 112,
2375                next_offset: 152,
2376                ..Default::default()
2377            }
2378            .as_bytes(),
2379        );
2380        extend_with_standard_verdict(&mut entries_bytes, VERDICT_DROP);
2381
2382        // Entry 3: drop all ICMP packets.
2383        entries_bytes.extend_from_slice(
2384            ipt_entry {
2385                ip: ipt_ip { proto: IPPROTO_ICMP as u16, ..Default::default() },
2386                target_offset: 112,
2387                next_offset: 152,
2388                ..Default::default()
2389            }
2390            .as_bytes(),
2391        );
2392        extend_with_standard_verdict(&mut entries_bytes, VERDICT_DROP);
2393
2394        // Entry 4: drop all ICMPV6 packets.
2395        // Note: this rule doesn't make sense on a IPv4 table, but iptables will defer to Netstack
2396        // to report this error.
2397        entries_bytes.extend_from_slice(
2398            ipt_entry {
2399                ip: ipt_ip { proto: IPPROTO_ICMPV6 as u16, ..Default::default() },
2400                target_offset: 112,
2401                next_offset: 152,
2402                ..Default::default()
2403            }
2404            .as_bytes(),
2405        );
2406        extend_with_standard_verdict(&mut entries_bytes, VERDICT_DROP);
2407
2408        // Entry 5: policy of INPUT chain.
2409        let input_underflow = entries_bytes.len() as u32;
2410        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
2411
2412        // Start of FORWARD built-in chain.
2413        let forward_hook_entry = entries_bytes.len() as u32;
2414
2415        // Entry 6: policy of FORWARD chain.
2416        // Note: FORWARD chain has no other rules.
2417        let forward_underflow = entries_bytes.len() as u32;
2418        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
2419
2420        // Start of OUTPUT built-in chain.
2421        let output_hook_entry = entries_bytes.len() as u32;
2422
2423        // Entry 7: accept all packets going from wifi1 interface.
2424        entries_bytes.extend_from_slice(
2425            ipt_entry {
2426                ip: ipt_ip {
2427                    outiface: string_to_ascii_buffer("wifi1").unwrap(),
2428                    outiface_mask: [255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
2429                    ..Default::default()
2430                },
2431                target_offset: 112,
2432                next_offset: 152,
2433                ..Default::default()
2434            }
2435            .as_bytes(),
2436        );
2437        extend_with_standard_verdict(&mut entries_bytes, VERDICT_ACCEPT);
2438
2439        // Entry 8: policy of OUTPUT chain.
2440        let output_underflow = entries_bytes.len() as u32;
2441        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_DROP);
2442
2443        // Entry 9: end of input.
2444        extend_with_error_target_ipv4_entry(&mut entries_bytes, TARGET_ERROR);
2445
2446        let mut bytes = ipt_replace {
2447            name: string_to_ascii_buffer("filter").unwrap(),
2448            num_entries: 10,
2449            size: entries_bytes.len() as u32,
2450            valid_hooks: NfIpHooks::FILTER.bits(),
2451            hook_entry: [0, input_hook_entry, forward_hook_entry, output_hook_entry, 0],
2452            underflow: [0, input_underflow, forward_underflow, output_underflow, 0],
2453            ..Default::default()
2454        }
2455        .as_bytes()
2456        .to_owned();
2457
2458        bytes.extend(entries_bytes);
2459        bytes
2460    }
2461
2462    fn ipv6_table_with_ip_matchers() -> Vec<u8> {
2463        let mut entries_bytes = Vec::new();
2464
2465        // Start of INPUT built-in chain.
2466        let input_hook_entry = entries_bytes.len() as u32;
2467
2468        // Entry 0: drop TCP packets other than from `IPV6_SUBNET`.
2469        entries_bytes.extend_from_slice(
2470            ip6t_entry {
2471                ipv6: ip6t_ip6 {
2472                    src: ipv6_subnet_addr(),
2473                    smsk: ipv6_subnet_mask(),
2474                    invflags: IPT_INV_SRCIP as u8,
2475                    proto: IPPROTO_TCP as u16,
2476                    flags: IP6T_F_PROTO as u8,
2477                    ..Default::default()
2478                },
2479                target_offset: 168,
2480                next_offset: 208,
2481                ..Default::default()
2482            }
2483            .as_bytes(),
2484        );
2485        extend_with_standard_verdict(&mut entries_bytes, VERDICT_DROP);
2486
2487        // Entry 1: accept UDP packets from `IPV6_SUBNET`.
2488        entries_bytes.extend_from_slice(
2489            ip6t_entry {
2490                ipv6: ip6t_ip6 {
2491                    src: ipv6_subnet_addr(),
2492                    smsk: ipv6_subnet_mask(),
2493                    proto: IPPROTO_UDP as u16,
2494                    flags: IP6T_F_PROTO as u8,
2495                    ..Default::default()
2496                },
2497                target_offset: 168,
2498                next_offset: 208,
2499                ..Default::default()
2500            }
2501            .as_bytes(),
2502        );
2503        extend_with_standard_verdict(&mut entries_bytes, VERDICT_ACCEPT);
2504
2505        // Entry 2: drop all packets going to en0 interface.
2506        entries_bytes.extend_from_slice(
2507            ip6t_entry {
2508                ipv6: ip6t_ip6 {
2509                    iniface: string_to_ascii_buffer("en0").unwrap(),
2510                    iniface_mask: [255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
2511                    ..Default::default()
2512                },
2513                target_offset: 168,
2514                next_offset: 208,
2515                ..Default::default()
2516            }
2517            .as_bytes(),
2518        );
2519        extend_with_standard_verdict(&mut entries_bytes, VERDICT_DROP);
2520
2521        // Entry 3: drop all ICMP packets.
2522        entries_bytes.extend_from_slice(
2523            ip6t_entry {
2524                ipv6: ip6t_ip6 {
2525                    proto: IPPROTO_ICMP as u16,
2526                    flags: IP6T_F_PROTO as u8,
2527                    ..Default::default()
2528                },
2529                target_offset: 168,
2530                next_offset: 208,
2531                ..Default::default()
2532            }
2533            .as_bytes(),
2534        );
2535        extend_with_standard_verdict(&mut entries_bytes, VERDICT_DROP);
2536
2537        // Entry 4: drop all ICMPV6 packets.
2538        // Note: this rule doesn't make sense on a IPv4 table, but iptables will defer to Netstack
2539        // to report this error.
2540        entries_bytes.extend_from_slice(
2541            ip6t_entry {
2542                ipv6: ip6t_ip6 {
2543                    proto: IPPROTO_ICMPV6 as u16,
2544                    flags: IP6T_F_PROTO as u8,
2545                    ..Default::default()
2546                },
2547                target_offset: 168,
2548                next_offset: 208,
2549                ..Default::default()
2550            }
2551            .as_bytes(),
2552        );
2553        extend_with_standard_verdict(&mut entries_bytes, VERDICT_DROP);
2554
2555        // Entry 5: policy of INPUT chain.
2556        let input_underflow = entries_bytes.len() as u32;
2557        extend_with_standard_target_ipv6_entry(&mut entries_bytes, VERDICT_ACCEPT);
2558
2559        // Start of FORWARD built-in chain.
2560        let forward_hook_entry = entries_bytes.len() as u32;
2561
2562        // Entry 6: policy of FORWARD chain.
2563        // Note: FORWARD chain has no other rules.
2564        let forward_underflow = entries_bytes.len() as u32;
2565        extend_with_standard_target_ipv6_entry(&mut entries_bytes, VERDICT_ACCEPT);
2566
2567        // Start of OUTPUT built-in chain.
2568        let output_hook_entry = entries_bytes.len() as u32;
2569
2570        // Entry 7: accept all packets going out from wifi1 interface.
2571        entries_bytes.extend_from_slice(
2572            ip6t_entry {
2573                ipv6: ip6t_ip6 {
2574                    outiface: string_to_ascii_buffer("wifi1").unwrap(),
2575                    outiface_mask: [255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
2576                    ..Default::default()
2577                },
2578                target_offset: 168,
2579                next_offset: 208,
2580                ..Default::default()
2581            }
2582            .as_bytes(),
2583        );
2584        extend_with_standard_verdict(&mut entries_bytes, VERDICT_ACCEPT);
2585
2586        // Entry 8: policy of OUTPUT chain.
2587        let output_underflow = entries_bytes.len() as u32;
2588        extend_with_standard_target_ipv6_entry(&mut entries_bytes, VERDICT_DROP);
2589
2590        // Entry 9: end of input.
2591        extend_with_error_target_ipv6_entry(&mut entries_bytes, TARGET_ERROR);
2592
2593        let mut bytes = ip6t_replace {
2594            name: string_to_ascii_buffer("filter").unwrap(),
2595            num_entries: 10,
2596            size: entries_bytes.len() as u32,
2597            valid_hooks: NfIpHooks::FILTER.bits(),
2598            hook_entry: [0, input_hook_entry, forward_hook_entry, output_hook_entry, 0],
2599            underflow: [0, input_underflow, forward_underflow, output_underflow, 0],
2600            ..Default::default()
2601        }
2602        .as_bytes()
2603        .to_owned();
2604
2605        bytes.append(&mut entries_bytes);
2606        bytes
2607    }
2608
2609    #[test_case(
2610        ipv4_table_with_ip_matchers(),
2611        IpTable::from_ipt_replace,
2612        filter_namespace_v4(),
2613        IPV4_SUBNET;
2614        "ipv4"
2615    )]
2616    #[test_case(
2617        ipv6_table_with_ip_matchers(),
2618        IpTable::from_ip6t_replace,
2619        filter_namespace_v6(),
2620        IPV6_SUBNET;
2621        "ipv6"
2622    )]
2623    fn parse_ip_matchers_test(
2624        table_bytes: Vec<u8>,
2625        parse_fn: fn(&mut TestIpTablesContext, Vec<u8>) -> Result<IpTable, IpTableParseError>,
2626        expected_namespace: fnet_filter_ext::Namespace,
2627        expected_subnet: fnet::Subnet,
2628    ) {
2629        let mut context = TestIpTablesContext::new();
2630        let table = parse_fn(&mut context, table_bytes).unwrap();
2631        assert_eq!(table.namespace, expected_namespace);
2632
2633        assert_eq!(
2634            table.routines,
2635            [
2636                filter_input_routine(&expected_namespace),
2637                filter_forward_routine(&expected_namespace),
2638                filter_output_routine(&expected_namespace),
2639            ]
2640        );
2641
2642        let expected_rules = [
2643            fnet_filter_ext::Rule {
2644                id: fnet_filter_ext::RuleId {
2645                    index: 0,
2646                    routine: filter_input_routine(&expected_namespace).id.clone(),
2647                },
2648                matchers: fnet_filter_ext::Matchers {
2649                    src_addr: Some(fnet_matchers_ext::Address {
2650                        matcher: fnet_matchers_ext::AddressMatcherType::Subnet(
2651                            fnet_matchers_ext::Subnet::try_from(expected_subnet).unwrap(),
2652                        ),
2653                        invert: true,
2654                    }),
2655                    transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
2656                        src_port: None,
2657                        dst_port: None,
2658                    }),
2659                    ..Default::default()
2660                },
2661                action: fnet_filter_ext::Action::Drop,
2662            },
2663            fnet_filter_ext::Rule {
2664                id: fnet_filter_ext::RuleId {
2665                    index: 1,
2666                    routine: filter_input_routine(&expected_namespace).id.clone(),
2667                },
2668                matchers: fnet_filter_ext::Matchers {
2669                    src_addr: Some(fnet_matchers_ext::Address {
2670                        matcher: fnet_matchers_ext::AddressMatcherType::Subnet(
2671                            fnet_matchers_ext::Subnet::try_from(expected_subnet).unwrap(),
2672                        ),
2673                        invert: false,
2674                    }),
2675                    transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Udp {
2676                        src_port: None,
2677                        dst_port: None,
2678                    }),
2679                    ..Default::default()
2680                },
2681                action: fnet_filter_ext::Action::Accept,
2682            },
2683            fnet_filter_ext::Rule {
2684                id: fnet_filter_ext::RuleId {
2685                    index: 2,
2686                    routine: filter_input_routine(&expected_namespace).id.clone(),
2687                },
2688                matchers: fnet_filter_ext::Matchers {
2689                    in_interface: Some(fnet_matchers_ext::Interface::Name("en0".to_string())),
2690                    ..Default::default()
2691                },
2692                action: fnet_filter_ext::Action::Drop,
2693            },
2694            fnet_filter_ext::Rule {
2695                id: fnet_filter_ext::RuleId {
2696                    index: 3,
2697                    routine: filter_input_routine(&expected_namespace).id.clone(),
2698                },
2699                matchers: fnet_filter_ext::Matchers {
2700                    transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Icmp),
2701                    ..Default::default()
2702                },
2703                action: fnet_filter_ext::Action::Drop,
2704            },
2705            fnet_filter_ext::Rule {
2706                id: fnet_filter_ext::RuleId {
2707                    index: 4,
2708                    routine: filter_input_routine(&expected_namespace).id.clone(),
2709                },
2710                matchers: fnet_filter_ext::Matchers {
2711                    transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Icmpv6),
2712                    ..Default::default()
2713                },
2714                action: fnet_filter_ext::Action::Drop,
2715            },
2716            fnet_filter_ext::Rule {
2717                id: fnet_filter_ext::RuleId {
2718                    index: 5,
2719                    routine: filter_input_routine(&expected_namespace).id.clone(),
2720                },
2721                matchers: fnet_filter_ext::Matchers::default(),
2722                action: fnet_filter_ext::Action::Accept,
2723            },
2724            fnet_filter_ext::Rule {
2725                id: fnet_filter_ext::RuleId {
2726                    index: 0,
2727                    routine: filter_forward_routine(&expected_namespace).id.clone(),
2728                },
2729                matchers: fnet_filter_ext::Matchers::default(),
2730                action: fnet_filter_ext::Action::Accept,
2731            },
2732            fnet_filter_ext::Rule {
2733                id: fnet_filter_ext::RuleId {
2734                    index: 0,
2735                    routine: filter_output_routine(&expected_namespace).id.clone(),
2736                },
2737                matchers: fnet_filter_ext::Matchers {
2738                    out_interface: Some(fnet_matchers_ext::Interface::Name("wifi1".to_string())),
2739                    ..Default::default()
2740                },
2741                action: fnet_filter_ext::Action::Accept,
2742            },
2743            fnet_filter_ext::Rule {
2744                id: fnet_filter_ext::RuleId {
2745                    index: 1,
2746                    routine: filter_output_routine(&expected_namespace).id.clone(),
2747                },
2748                matchers: fnet_filter_ext::Matchers::default(),
2749                action: fnet_filter_ext::Action::Drop,
2750            },
2751        ];
2752
2753        for (rule, expected) in table.rules.iter().zip_eq(expected_rules.into_iter()) {
2754            assert_eq!(rule, &expected);
2755        }
2756    }
2757
2758    const TEST_EBPF_PROGRAM_PATH: &str = "/sys/fs/bpf/test_prog";
2759    const TEST_EBPF_PROGRAM_ID: febpf::ProgramId = febpf::ProgramId { id: 42 };
2760
2761    fn table_with_match_extensions() -> Vec<u8> {
2762        let mut entries_bytes = Vec::new();
2763
2764        // Start of INPUT built-in chain.
2765        let input_hook_entry = entries_bytes.len() as u32;
2766
2767        // Entry 0: DROP all TCP packets except those destined to port 8000. Offset 0
2768        entries_bytes.extend_from_slice(
2769            ipt_entry {
2770                ip: ipt_ip { proto: IPPROTO_TCP as u16, ..Default::default() },
2771                target_offset: 160,
2772                next_offset: 200,
2773                ..Default::default()
2774            }
2775            .as_bytes(),
2776        );
2777        entries_bytes.extend_from_slice(
2778            xt_entry_match {
2779                match_size: 48,
2780                name: string_to_ascii_buffer("tcp").unwrap(),
2781                revision: 0,
2782            }
2783            .as_bytes(),
2784        );
2785        entries_bytes.extend_from_slice(
2786            xt_tcp {
2787                spts: [0, 65535],
2788                dpts: [8000, 8000],
2789                invflags: XT_TCP_INV_DSTPT as u8,
2790                ..Default::default()
2791            }
2792            .as_bytes(),
2793        );
2794        entries_bytes.extend_from_slice(&[0, 0, 0, 0]); // padding
2795        extend_with_standard_verdict(&mut entries_bytes, VERDICT_DROP);
2796
2797        // Entry 1: ACCEPT UDP packets with source port between 2000-3000.
2798        entries_bytes.extend_from_slice(
2799            ipt_entry {
2800                ip: ipt_ip { proto: IPPROTO_UDP as u16, ..Default::default() },
2801                target_offset: 160,
2802                next_offset: 200,
2803                ..Default::default()
2804            }
2805            .as_bytes(),
2806        );
2807        entries_bytes.extend_from_slice(
2808            xt_entry_match {
2809                match_size: 48,
2810                name: string_to_ascii_buffer("udp").unwrap(),
2811                revision: 0,
2812            }
2813            .as_bytes(),
2814        );
2815        entries_bytes.extend_from_slice(
2816            xt_udp { spts: [2000, 3000], dpts: [0, 65535], ..Default::default() }.as_bytes(),
2817        );
2818        entries_bytes.extend_from_slice(&[0, 0, 0, 0, 0, 0]);
2819        extend_with_standard_verdict(&mut entries_bytes, VERDICT_ACCEPT);
2820
2821        // Entry 2: Drop packets that match custom BPF matcher.
2822        const BPF_MATCHER_SIZE: usize = size_of::<xt_entry_match>() + size_of::<xt_bpf_info_v1>();
2823        const BPF_TARGET_OFFSET: usize = BPF_MATCHER_SIZE + size_of::<ipt_entry>();
2824        entries_bytes.extend_from_slice(
2825            ipt_entry {
2826                ip: Default::default(),
2827                target_offset: BPF_TARGET_OFFSET as u16,
2828                next_offset: (BPF_TARGET_OFFSET + STANDARD_TARGET_SIZE) as u16,
2829                ..Default::default()
2830            }
2831            .as_bytes(),
2832        );
2833        entries_bytes.extend_from_slice(
2834            xt_entry_match {
2835                match_size: BPF_MATCHER_SIZE as u16,
2836                name: string_to_ascii_buffer("bpf").unwrap(),
2837                revision: 1,
2838            }
2839            .as_bytes(),
2840        );
2841        let bpf_info = xt_bpf_info_v1 {
2842            mode: starnix_uapi::xt_bpf_modes_XT_BPF_MODE_FD_PINNED as u16,
2843            bpf_program_num_elem: 0,
2844            fd: 0,
2845            __bindgen_anon_1: starnix_uapi::xt_bpf_info_v1__bindgen_ty_1 {
2846                path: string_to_ascii_buffer(TEST_EBPF_PROGRAM_PATH).unwrap(),
2847            },
2848            filter: starnix_uapi::uaddr::default().into(),
2849        };
2850        entries_bytes.extend_from_slice(bpf_info.as_bytes());
2851        extend_with_standard_verdict(&mut entries_bytes, VERDICT_ACCEPT);
2852
2853        // Entry 3: policy of INPUT chain.
2854        let input_underflow = entries_bytes.len() as u32;
2855        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
2856
2857        // Start of FORWARD built-in chain.
2858        let forward_hook_entry = entries_bytes.len() as u32;
2859
2860        // Entry 4: policy of FORWARD chain.
2861        // Note: FORWARD chain has no other rules.
2862        let forward_underflow = entries_bytes.len() as u32;
2863        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
2864
2865        // Start of OUTPUT built-in chain.
2866        let output_hook_entry = entries_bytes.len() as u32;
2867
2868        // Entry 5: policy of OUTPUT chain.
2869        // Note: OUTPUT chain has no other rules.
2870        let output_underflow = entries_bytes.len() as u32;
2871        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_DROP);
2872
2873        // Entry 6: end of input.
2874        extend_with_error_target_ipv4_entry(&mut entries_bytes, TARGET_ERROR);
2875
2876        let mut bytes = ipt_replace {
2877            name: string_to_ascii_buffer("filter").unwrap(),
2878            num_entries: 7,
2879            size: entries_bytes.len() as u32,
2880            valid_hooks: NfIpHooks::FILTER.bits(),
2881            hook_entry: [0, input_hook_entry, forward_hook_entry, output_hook_entry, 0],
2882            underflow: [0, input_underflow, forward_underflow, output_underflow, 0],
2883            ..Default::default()
2884        }
2885        .as_bytes()
2886        .to_owned();
2887        bytes.extend(entries_bytes);
2888        bytes
2889    }
2890
2891    #[fuchsia::test]
2892    fn parse_match_extensions_test() {
2893        let mut context = TestIpTablesContext::new();
2894        let table = IpTable::from_ipt_replace(&mut context, table_with_match_extensions()).unwrap();
2895
2896        let expected_namespace = filter_namespace_v4();
2897        assert_eq!(table.namespace, expected_namespace);
2898
2899        assert_eq!(
2900            table.routines,
2901            [
2902                filter_input_routine(&expected_namespace),
2903                filter_forward_routine(&expected_namespace),
2904                filter_output_routine(&expected_namespace),
2905            ]
2906        );
2907
2908        let mut rules = table.rules.into_iter();
2909
2910        assert_eq!(
2911            rules.next().unwrap(),
2912            fnet_filter_ext::Rule {
2913                id: fnet_filter_ext::RuleId {
2914                    index: 0,
2915                    routine: filter_input_routine(&expected_namespace).id,
2916                },
2917                matchers: fnet_filter_ext::Matchers {
2918                    transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
2919                        src_port: Some(fnet_matchers_ext::Port::new(0, 65535, false).unwrap()),
2920                        dst_port: Some(fnet_matchers_ext::Port::new(8000, 8000, true).unwrap()),
2921                    }),
2922                    ..Default::default()
2923                },
2924                action: fnet_filter_ext::Action::Drop,
2925            }
2926        );
2927        assert_eq!(
2928            rules.next().unwrap(),
2929            fnet_filter_ext::Rule {
2930                id: fnet_filter_ext::RuleId {
2931                    index: 1,
2932                    routine: filter_input_routine(&expected_namespace).id,
2933                },
2934                matchers: fnet_filter_ext::Matchers {
2935                    transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Udp {
2936                        src_port: Some(fnet_matchers_ext::Port::new(2000, 3000, false).unwrap()),
2937                        dst_port: Some(fnet_matchers_ext::Port::new(0, 65535, false).unwrap()),
2938                    }),
2939                    ..Default::default()
2940                },
2941                action: fnet_filter_ext::Action::Accept,
2942            }
2943        );
2944        assert_eq!(
2945            rules.next().unwrap(),
2946            fnet_filter_ext::Rule {
2947                id: fnet_filter_ext::RuleId {
2948                    index: 2,
2949                    routine: filter_input_routine(&expected_namespace).id,
2950                },
2951                matchers: fnet_filter_ext::Matchers {
2952                    ebpf_program: Some(TEST_EBPF_PROGRAM_ID),
2953                    ..Default::default()
2954                },
2955                action: fnet_filter_ext::Action::Accept,
2956            }
2957        );
2958
2959        assert_eq!(
2960            rules.next().unwrap(),
2961            fnet_filter_ext::Rule {
2962                id: fnet_filter_ext::RuleId {
2963                    index: 3,
2964                    routine: filter_input_routine(&expected_namespace).id,
2965                },
2966                matchers: fnet_filter_ext::Matchers::default(),
2967                action: fnet_filter_ext::Action::Accept,
2968            }
2969        );
2970
2971        assert_eq!(
2972            rules.next().unwrap(),
2973            fnet_filter_ext::Rule {
2974                id: fnet_filter_ext::RuleId {
2975                    index: 0,
2976                    routine: filter_forward_routine(&expected_namespace).id,
2977                },
2978                matchers: fnet_filter_ext::Matchers::default(),
2979                action: fnet_filter_ext::Action::Accept,
2980            }
2981        );
2982
2983        assert_eq!(
2984            rules.next().unwrap(),
2985            fnet_filter_ext::Rule {
2986                id: fnet_filter_ext::RuleId {
2987                    index: 0,
2988                    routine: filter_output_routine(&expected_namespace).id,
2989                },
2990                matchers: fnet_filter_ext::Matchers::default(),
2991                action: fnet_filter_ext::Action::Drop,
2992            }
2993        );
2994
2995        assert!(rules.next().is_none());
2996    }
2997
2998    fn ipv4_table_with_jump_target() -> Vec<u8> {
2999        let mut entries_bytes = Vec::new();
3000
3001        // Start of INPUT built-in chain.
3002        let input_hook_entry = entries_bytes.len() as u32;
3003
3004        // Entry 0 (byte 0): JUMP to chain1 for all packets.
3005        extend_with_standard_target_ipv4_entry(&mut entries_bytes, 784);
3006
3007        // Entry 1 (byte 152): policy of INPUT chain.
3008        let input_underflow = entries_bytes.len() as u32;
3009        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
3010
3011        // Start of FORWARD built-in chain.
3012        let forward_hook_entry = entries_bytes.len() as u32;
3013
3014        // Entry 2 (byte 304): policy of FORWARD chain.
3015        // Note: FORWARD chain has no other rules.
3016        let forward_underflow = entries_bytes.len() as u32;
3017        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
3018
3019        // Start of OUTPUT built-in chain.
3020        let output_hook_entry = entries_bytes.len() as u32;
3021
3022        // Entry 3 (byte 456): policy of OUTPUT chain.
3023        // Note: OUTPUT chain has no other rules.
3024        let output_underflow = entries_bytes.len() as u32;
3025        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_DROP);
3026
3027        // Entry 4 (byte 608): start of the chain1.
3028        extend_with_error_target_ipv4_entry(&mut entries_bytes, "chain1");
3029
3030        // Entry 5 (byte 784): jump to chain2 for all packets.
3031        extend_with_standard_target_ipv4_entry(&mut entries_bytes, 1264);
3032
3033        // Entry 6 (byte 936): policy of chain1.
3034        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_RETURN);
3035
3036        // Entry 7 (byte 1088): start of chain2.
3037        extend_with_error_target_ipv4_entry(&mut entries_bytes, "chain2");
3038
3039        // Entry 8 (byte 1264): policy of chain2.
3040        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_RETURN);
3041
3042        // Entry 9 (byte 1416): end of input.
3043        extend_with_error_target_ipv4_entry(&mut entries_bytes, TARGET_ERROR);
3044
3045        let mut bytes = ipt_replace {
3046            name: string_to_ascii_buffer("filter").unwrap(),
3047            num_entries: 10,
3048            size: entries_bytes.len() as u32,
3049            valid_hooks: NfIpHooks::FILTER.bits(),
3050            hook_entry: [0, input_hook_entry, forward_hook_entry, output_hook_entry, 0],
3051            underflow: [0, input_underflow, forward_underflow, output_underflow, 0],
3052            ..Default::default()
3053        }
3054        .as_bytes()
3055        .to_owned();
3056
3057        bytes.extend(entries_bytes);
3058        bytes
3059    }
3060
3061    fn ipv6_table_with_jump_target() -> Vec<u8> {
3062        let mut entries_bytes = Vec::new();
3063
3064        // Start of INPUT built-in chain.
3065        let input_hook_entry = entries_bytes.len() as u32;
3066
3067        // Entry 0 (byte 0): JUMP to chain1 for all packets.
3068        extend_with_standard_target_ipv6_entry(&mut entries_bytes, 1064);
3069
3070        // Entry 1 (byte 208): policy of INPUT chain.
3071        let input_underflow = entries_bytes.len() as u32;
3072        extend_with_standard_target_ipv6_entry(&mut entries_bytes, VERDICT_ACCEPT);
3073
3074        // Start of FORWARD built-in chain.
3075        let forward_hook_entry = entries_bytes.len() as u32;
3076
3077        // Entry 2 (byte 416): policy of FORWARD chain.
3078        // Note: FORWARD chain has no other rules.
3079        let forward_underflow = entries_bytes.len() as u32;
3080        extend_with_standard_target_ipv6_entry(&mut entries_bytes, VERDICT_ACCEPT);
3081
3082        // Start of OUTPUT built-in chain.
3083        let output_hook_entry = entries_bytes.len() as u32;
3084
3085        // Entry 3 (byte 624): policy of OUTPUT chain.
3086        // Note: OUTPUT chain has no other rules.
3087        let output_underflow = entries_bytes.len() as u32;
3088        extend_with_standard_target_ipv6_entry(&mut entries_bytes, VERDICT_DROP);
3089
3090        // Entry 4 (byte 832): start of the chain1.
3091        extend_with_error_target_ipv6_entry(&mut entries_bytes, "chain1");
3092
3093        // Entry 5 (byte 1064): jump to chain2 for all packets.
3094        extend_with_standard_target_ipv6_entry(&mut entries_bytes, 1712);
3095
3096        // Entry 6 (byte 1272): policy of chain1.
3097        extend_with_standard_target_ipv6_entry(&mut entries_bytes, VERDICT_RETURN);
3098
3099        // Entry 7 (byte 1480): start of chain2.
3100        extend_with_error_target_ipv6_entry(&mut entries_bytes, "chain2");
3101
3102        // Entry 8 (byte 1712): policy of chain2.
3103        extend_with_standard_target_ipv6_entry(&mut entries_bytes, VERDICT_RETURN);
3104
3105        // Entry 9 (byte 1920): end of input.
3106        extend_with_error_target_ipv6_entry(&mut entries_bytes, TARGET_ERROR);
3107
3108        let mut bytes = ip6t_replace {
3109            name: string_to_ascii_buffer("filter").unwrap(),
3110            num_entries: 10,
3111            size: entries_bytes.len() as u32,
3112            valid_hooks: NfIpHooks::FILTER.bits(),
3113            hook_entry: [0, input_hook_entry, forward_hook_entry, output_hook_entry, 0],
3114            underflow: [0, input_underflow, forward_underflow, output_underflow, 0],
3115            ..Default::default()
3116        }
3117        .as_bytes()
3118        .to_owned();
3119
3120        bytes.extend(entries_bytes);
3121        bytes
3122    }
3123
3124    #[test_case(
3125        ipv4_table_with_jump_target(),
3126        IpTable::from_ipt_replace,
3127        filter_namespace_v4();
3128        "ipv4"
3129    )]
3130    #[test_case(
3131        ipv6_table_with_jump_target(),
3132        IpTable::from_ip6t_replace,
3133        filter_namespace_v6();
3134        "ipv6"
3135    )]
3136    fn parse_jump_target_test(
3137        table_bytes: Vec<u8>,
3138        parse_fn: fn(&mut TestIpTablesContext, Vec<u8>) -> Result<IpTable, IpTableParseError>,
3139        expected_namespace: fnet_filter_ext::Namespace,
3140    ) {
3141        let mut context = TestIpTablesContext::new();
3142        let table = parse_fn(&mut context, table_bytes).unwrap();
3143        assert_eq!(table.namespace, expected_namespace);
3144
3145        assert_eq!(
3146            table.routines,
3147            [
3148                filter_input_routine(&expected_namespace),
3149                filter_forward_routine(&expected_namespace),
3150                filter_output_routine(&expected_namespace),
3151                filter_custom_routine(&expected_namespace, "chain1"),
3152                filter_custom_routine(&expected_namespace, "chain2")
3153            ]
3154        );
3155
3156        let expected_rules = [
3157            fnet_filter_ext::Rule {
3158                id: fnet_filter_ext::RuleId {
3159                    index: 0,
3160                    routine: filter_input_routine(&expected_namespace).id.clone(),
3161                },
3162                matchers: fnet_filter_ext::Matchers::default(),
3163                action: fnet_filter_ext::Action::Jump("chain1".to_string()),
3164            },
3165            fnet_filter_ext::Rule {
3166                id: fnet_filter_ext::RuleId {
3167                    index: 1,
3168                    routine: filter_input_routine(&expected_namespace).id.clone(),
3169                },
3170                matchers: fnet_filter_ext::Matchers::default(),
3171                action: fnet_filter_ext::Action::Accept,
3172            },
3173            fnet_filter_ext::Rule {
3174                id: fnet_filter_ext::RuleId {
3175                    index: 0,
3176                    routine: filter_forward_routine(&expected_namespace).id.clone(),
3177                },
3178                matchers: fnet_filter_ext::Matchers::default(),
3179                action: fnet_filter_ext::Action::Accept,
3180            },
3181            fnet_filter_ext::Rule {
3182                id: fnet_filter_ext::RuleId {
3183                    index: 0,
3184                    routine: filter_output_routine(&expected_namespace).id.clone(),
3185                },
3186                matchers: fnet_filter_ext::Matchers::default(),
3187                action: fnet_filter_ext::Action::Drop,
3188            },
3189            fnet_filter_ext::Rule {
3190                id: fnet_filter_ext::RuleId {
3191                    index: 0,
3192                    routine: filter_custom_routine(&expected_namespace, "chain1").id.clone(),
3193                },
3194                matchers: fnet_filter_ext::Matchers::default(),
3195                action: fnet_filter_ext::Action::Jump("chain2".to_string()),
3196            },
3197            fnet_filter_ext::Rule {
3198                id: fnet_filter_ext::RuleId {
3199                    index: 1,
3200                    routine: filter_custom_routine(&expected_namespace, "chain1").id.clone(),
3201                },
3202                matchers: fnet_filter_ext::Matchers::default(),
3203                action: fnet_filter_ext::Action::Return,
3204            },
3205            fnet_filter_ext::Rule {
3206                id: fnet_filter_ext::RuleId {
3207                    index: 0,
3208                    routine: filter_custom_routine(&expected_namespace, "chain2").id.clone(),
3209                },
3210                matchers: fnet_filter_ext::Matchers::default(),
3211                action: fnet_filter_ext::Action::Return,
3212            },
3213        ];
3214
3215        for (rule, expected) in table.rules.iter().zip_eq(expected_rules.into_iter()) {
3216            assert_eq!(rule, &expected);
3217        }
3218    }
3219
3220    // Same layout as `nf_nat_ipv4_multi_range_compat`.
3221    #[repr(C)]
3222    #[derive(IntoBytes, Debug, Default, KnownLayout, FromBytes, Immutable)]
3223    struct RedirectTargetV4 {
3224        pub rangesize: u32,
3225        pub flags: u32,
3226        pub _min_ip: u32,
3227        pub _max_ip: u32,
3228        pub min: u16,
3229        pub max: u16,
3230    }
3231
3232    // Same layout as `xt_tproxy_target_info_v1` with an IPv4 address.
3233    #[repr(C)]
3234    #[derive(IntoBytes, Default, KnownLayout, FromBytes, Immutable)]
3235    struct TproxyTargetV4 {
3236        pub _mark_mask: u32,
3237        pub _mark_value: u32,
3238        pub laddr: in_addr,
3239        pub _addr_padding: [u32; 3],
3240        pub lport: u16,
3241        pub _padding: [u8; 2],
3242    }
3243
3244    fn ipv4_table_with_redirect_and_tproxy() -> Vec<u8> {
3245        let mut entries_bytes = Vec::new();
3246
3247        // Start of PREROUTING built-in chain.
3248        let prerouting_hook_entry = entries_bytes.len() as u32;
3249
3250        // Entry 0: TPROXY TCP traffic to addr.
3251        entries_bytes.extend_from_slice(
3252            ipt_entry {
3253                ip: ipt_ip { proto: IPPROTO_TCP as u16, ..Default::default() },
3254                target_offset: 112,
3255                next_offset: 172,
3256                ..Default::default()
3257            }
3258            .as_bytes(),
3259        );
3260        entries_bytes.extend_from_slice(
3261            xt_entry_target {
3262                target_size: 60,
3263                name: string_to_ascii_buffer(TARGET_TPROXY).unwrap(),
3264                revision: 1,
3265            }
3266            .as_bytes(),
3267        );
3268        entries_bytes.extend_from_slice(
3269            TproxyTargetV4 { laddr: ipv4_subnet_addr(), ..Default::default() }.as_bytes(),
3270        );
3271
3272        // Entry 1: TPROXY UDP traffic to port.
3273        entries_bytes.extend_from_slice(
3274            ipt_entry {
3275                ip: ipt_ip { proto: IPPROTO_UDP as u16, ..Default::default() },
3276                target_offset: 112,
3277                next_offset: 172,
3278                ..Default::default()
3279            }
3280            .as_bytes(),
3281        );
3282        entries_bytes.extend_from_slice(
3283            xt_entry_target {
3284                target_size: 60,
3285                name: string_to_ascii_buffer(TARGET_TPROXY).unwrap(),
3286                revision: 1,
3287            }
3288            .as_bytes(),
3289        );
3290        entries_bytes
3291            .extend_from_slice(TproxyTargetV4 { lport: PORT, ..Default::default() }.as_bytes());
3292
3293        // Entry 2: TPROXY TCP traffic to addr and port.
3294        entries_bytes.extend_from_slice(
3295            ipt_entry {
3296                ip: ipt_ip { proto: IPPROTO_TCP as u16, ..Default::default() },
3297                target_offset: 112,
3298                next_offset: 172,
3299                ..Default::default()
3300            }
3301            .as_bytes(),
3302        );
3303        entries_bytes.extend_from_slice(
3304            xt_entry_target {
3305                target_size: 60,
3306                name: string_to_ascii_buffer(TARGET_TPROXY).unwrap(),
3307                revision: 1,
3308            }
3309            .as_bytes(),
3310        );
3311        entries_bytes.extend_from_slice(
3312            TproxyTargetV4 { laddr: ipv4_subnet_addr(), lport: PORT, ..Default::default() }
3313                .as_bytes(),
3314        );
3315
3316        // Entry 3: policy of PREROUTING chain.
3317        let prerouting_underflow = entries_bytes.len() as u32;
3318        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
3319
3320        // Start of INPUT built-in chain.
3321        let input_hook_entry = entries_bytes.len() as u32;
3322
3323        // Entry 4: policy of INPUT chain.
3324        // Note: INPUT chain has no other rules.
3325        let input_underflow = entries_bytes.len() as u32;
3326        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
3327
3328        // Start of OUTPUT built-in chain.
3329        let output_hook_entry = entries_bytes.len() as u32;
3330
3331        // Entry 5: REDIRECT TCP traffic without port change.
3332        entries_bytes.extend_from_slice(
3333            ipt_entry {
3334                ip: ipt_ip { proto: IPPROTO_TCP as u16, ..Default::default() },
3335                target_offset: 112,
3336                next_offset: 164,
3337                ..Default::default()
3338            }
3339            .as_bytes(),
3340        );
3341        entries_bytes.extend_from_slice(
3342            xt_entry_target {
3343                target_size: 52,
3344                name: string_to_ascii_buffer(TARGET_REDIRECT).unwrap(),
3345                revision: 0,
3346            }
3347            .as_bytes(),
3348        );
3349        entries_bytes.extend_from_slice(
3350            RedirectTargetV4 { rangesize: 1, flags: 0, ..Default::default() }.as_bytes(),
3351        );
3352
3353        // Entry 6: REDIRECT UDP traffic to a single port.
3354        entries_bytes.extend_from_slice(
3355            ipt_entry {
3356                ip: ipt_ip { proto: IPPROTO_UDP as u16, ..Default::default() },
3357                target_offset: 112,
3358                next_offset: 164,
3359                ..Default::default()
3360            }
3361            .as_bytes(),
3362        );
3363        entries_bytes.extend_from_slice(
3364            xt_entry_target {
3365                target_size: 52,
3366                name: string_to_ascii_buffer(TARGET_REDIRECT).unwrap(),
3367                revision: 0,
3368            }
3369            .as_bytes(),
3370        );
3371        entries_bytes.extend_from_slice(
3372            RedirectTargetV4 {
3373                rangesize: 1,
3374                flags: NfNatRangeFlags::PROTO_SPECIFIED.bits(),
3375                min: PORT,
3376                max: PORT,
3377                ..Default::default()
3378            }
3379            .as_bytes(),
3380        );
3381
3382        // Entry 7: REDIRECT TCP traffic to a port range.
3383        entries_bytes.extend_from_slice(
3384            ipt_entry {
3385                ip: ipt_ip { proto: IPPROTO_TCP as u16, ..Default::default() },
3386                target_offset: 112,
3387                next_offset: 164,
3388                ..Default::default()
3389            }
3390            .as_bytes(),
3391        );
3392        entries_bytes.extend_from_slice(
3393            xt_entry_target {
3394                target_size: 52,
3395                name: string_to_ascii_buffer(TARGET_REDIRECT).unwrap(),
3396                revision: 0,
3397            }
3398            .as_bytes(),
3399        );
3400        entries_bytes.extend_from_slice(
3401            RedirectTargetV4 {
3402                rangesize: 1,
3403                flags: NfNatRangeFlags::PROTO_SPECIFIED.bits(),
3404                min: PORT_RANGE_START,
3405                max: PORT_RANGE_END,
3406                ..Default::default()
3407            }
3408            .as_bytes(),
3409        );
3410
3411        // Entry 8: policy of OUTPUT chain.
3412        // Note: OUTPUT chain has no other rules.
3413        let output_underflow = entries_bytes.len() as u32;
3414        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
3415
3416        // Start of POSTROUTING built-in chain.
3417        let postrouting_hook_entry = entries_bytes.len() as u32;
3418
3419        // Entry 9: policy of POSTROUTING chain.
3420        // Note: POSTROUTING chain has no other rules.
3421        let postrouting_underflow = entries_bytes.len() as u32;
3422        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_DROP);
3423
3424        // Entry 10: end of input.
3425        extend_with_error_target_ipv4_entry(&mut entries_bytes, TARGET_ERROR);
3426
3427        let mut bytes = ipt_replace {
3428            name: string_to_ascii_buffer("nat").unwrap(),
3429            num_entries: 11,
3430            size: entries_bytes.len() as u32,
3431            valid_hooks: NfIpHooks::NAT.bits(),
3432            hook_entry: [
3433                prerouting_hook_entry,
3434                input_hook_entry,
3435                0,
3436                output_hook_entry,
3437                postrouting_hook_entry,
3438            ],
3439            underflow: [
3440                prerouting_underflow,
3441                input_underflow,
3442                0,
3443                output_underflow,
3444                postrouting_underflow,
3445            ],
3446            ..Default::default()
3447        }
3448        .as_bytes()
3449        .to_owned();
3450
3451        bytes.extend(entries_bytes);
3452        bytes
3453    }
3454
3455    // Same layout as `nf_nat_range`.
3456    #[repr(C)]
3457    #[derive(IntoBytes, Default, KnownLayout, FromBytes, Immutable)]
3458    struct RedirectTargetV6 {
3459        pub flags: u32,
3460        pub _min_addr: [u32; 4],
3461        pub _max_addr: [u32; 4],
3462        pub min_proto: u16,
3463        pub max_proto: u16,
3464    }
3465
3466    // Same layout as `xt_tproxy_target_info_v1` with an IPv6 address.
3467    #[repr(C)]
3468    #[derive(IntoBytes, Default, KnownLayout, FromBytes, Immutable)]
3469    struct TproxyTargetV6 {
3470        pub _mark_mask: u32,
3471        pub _mark_value: u32,
3472        pub laddr: in6_addr,
3473        pub lport: u16,
3474        pub _padding: [u8; 2],
3475    }
3476
3477    fn ipv6_table_with_redirect_and_tproxy() -> Vec<u8> {
3478        let mut entries_bytes = Vec::new();
3479
3480        // Start of PREROUTING built-in chain.
3481        let prerouting_hook_entry = entries_bytes.len() as u32;
3482
3483        // Entry 0: TPROXY TCP traffic to addr.
3484        entries_bytes.extend_from_slice(
3485            ip6t_entry {
3486                ipv6: ip6t_ip6 {
3487                    proto: IPPROTO_TCP as u16,
3488                    flags: IP6T_F_PROTO as u8,
3489                    ..Default::default()
3490                },
3491                target_offset: 168,
3492                next_offset: 228,
3493                ..Default::default()
3494            }
3495            .as_bytes(),
3496        );
3497        entries_bytes.extend_from_slice(
3498            xt_entry_target {
3499                target_size: 60,
3500                name: string_to_ascii_buffer(TARGET_TPROXY).unwrap(),
3501                revision: 1,
3502            }
3503            .as_bytes(),
3504        );
3505        entries_bytes.extend_from_slice(
3506            TproxyTargetV6 { laddr: ipv6_subnet_addr(), ..Default::default() }.as_bytes(),
3507        );
3508
3509        // Entry 1: TPROXY UDP traffic to port.
3510        entries_bytes.extend_from_slice(
3511            ip6t_entry {
3512                ipv6: ip6t_ip6 {
3513                    proto: IPPROTO_UDP as u16,
3514                    flags: IP6T_F_PROTO as u8,
3515                    ..Default::default()
3516                },
3517                target_offset: 168,
3518                next_offset: 228,
3519                ..Default::default()
3520            }
3521            .as_bytes(),
3522        );
3523        entries_bytes.extend_from_slice(
3524            xt_entry_target {
3525                target_size: 60,
3526                name: string_to_ascii_buffer(TARGET_TPROXY).unwrap(),
3527                revision: 1,
3528            }
3529            .as_bytes(),
3530        );
3531        entries_bytes
3532            .extend_from_slice(TproxyTargetV6 { lport: PORT, ..Default::default() }.as_bytes());
3533
3534        // Entry 2: TPROXY TCP traffic to addr and port.
3535        entries_bytes.extend_from_slice(
3536            ip6t_entry {
3537                ipv6: ip6t_ip6 {
3538                    proto: IPPROTO_TCP as u16,
3539                    flags: IP6T_F_PROTO as u8,
3540                    ..Default::default()
3541                },
3542                target_offset: 168,
3543                next_offset: 228,
3544                ..Default::default()
3545            }
3546            .as_bytes(),
3547        );
3548        entries_bytes.extend_from_slice(
3549            xt_entry_target {
3550                target_size: 60,
3551                name: string_to_ascii_buffer(TARGET_TPROXY).unwrap(),
3552                revision: 1,
3553            }
3554            .as_bytes(),
3555        );
3556        entries_bytes.extend_from_slice(
3557            TproxyTargetV6 { laddr: ipv6_subnet_addr(), lport: PORT, ..Default::default() }
3558                .as_bytes(),
3559        );
3560
3561        // Entry 3: policy of PREROUTING chain.
3562        let prerouting_underflow = entries_bytes.len() as u32;
3563        extend_with_standard_target_ipv6_entry(&mut entries_bytes, VERDICT_ACCEPT);
3564
3565        // Start of INPUT built-in chain.
3566        let input_hook_entry = entries_bytes.len() as u32;
3567
3568        // Entry 4: policy of INPUT chain.
3569        // Note: INPUT chain has no other rules.
3570        let input_underflow = entries_bytes.len() as u32;
3571        extend_with_standard_target_ipv6_entry(&mut entries_bytes, VERDICT_ACCEPT);
3572
3573        // Start of OUTPUT built-in chain.
3574        let output_hook_entry = entries_bytes.len() as u32;
3575
3576        // Entry 5: REDIRECT TCP traffic without port change.
3577        entries_bytes.extend_from_slice(
3578            ip6t_entry {
3579                ipv6: ip6t_ip6 {
3580                    proto: IPPROTO_TCP as u16,
3581                    flags: IP6T_F_PROTO as u8,
3582                    ..Default::default()
3583                },
3584                target_offset: 168,
3585                next_offset: 240,
3586                ..Default::default()
3587            }
3588            .as_bytes(),
3589        );
3590        entries_bytes.extend_from_slice(
3591            xt_entry_target {
3592                target_size: 72,
3593                name: string_to_ascii_buffer(TARGET_REDIRECT).unwrap(),
3594                revision: 0,
3595            }
3596            .as_bytes(),
3597        );
3598        entries_bytes
3599            .extend_from_slice(RedirectTargetV6 { flags: 0, ..Default::default() }.as_bytes());
3600
3601        // Entry 6: REDIRECT UDP traffic to a single port.
3602        entries_bytes.extend_from_slice(
3603            ip6t_entry {
3604                ipv6: ip6t_ip6 {
3605                    proto: IPPROTO_UDP as u16,
3606                    flags: IP6T_F_PROTO as u8,
3607                    ..Default::default()
3608                },
3609                target_offset: 168,
3610                next_offset: 240,
3611                ..Default::default()
3612            }
3613            .as_bytes(),
3614        );
3615        entries_bytes.extend_from_slice(
3616            xt_entry_target {
3617                target_size: 72,
3618                name: string_to_ascii_buffer(TARGET_REDIRECT).unwrap(),
3619                revision: 0,
3620            }
3621            .as_bytes(),
3622        );
3623        entries_bytes.extend_from_slice(
3624            RedirectTargetV6 {
3625                flags: NfNatRangeFlags::PROTO_SPECIFIED.bits(),
3626                min_proto: PORT,
3627                max_proto: PORT,
3628                ..Default::default()
3629            }
3630            .as_bytes(),
3631        );
3632
3633        // Entry 7: REDIRECT TCP traffic to a port range.
3634        entries_bytes.extend_from_slice(
3635            ip6t_entry {
3636                ipv6: ip6t_ip6 {
3637                    proto: IPPROTO_TCP as u16,
3638                    flags: IP6T_F_PROTO as u8,
3639                    ..Default::default()
3640                },
3641                target_offset: 168,
3642                next_offset: 240,
3643                ..Default::default()
3644            }
3645            .as_bytes(),
3646        );
3647        entries_bytes.extend_from_slice(
3648            xt_entry_target {
3649                target_size: 72,
3650                name: string_to_ascii_buffer(TARGET_REDIRECT).unwrap(),
3651                revision: 0,
3652            }
3653            .as_bytes(),
3654        );
3655        entries_bytes.extend_from_slice(
3656            RedirectTargetV6 {
3657                flags: NfNatRangeFlags::PROTO_SPECIFIED.bits(),
3658                min_proto: PORT_RANGE_START,
3659                max_proto: PORT_RANGE_END,
3660                ..Default::default()
3661            }
3662            .as_bytes(),
3663        );
3664
3665        // Entry 8: policy of OUTPUT chain.
3666        let output_underflow = entries_bytes.len() as u32;
3667        extend_with_standard_target_ipv6_entry(&mut entries_bytes, VERDICT_ACCEPT);
3668
3669        // Start of POSTROUTING built-in chain.
3670        let postrouting_hook_entry = entries_bytes.len() as u32;
3671
3672        // Entry 9: policy of POSTROUTING chain.
3673        // Note: POSTROUTING chain has no other rules.
3674        let postrouting_underflow = entries_bytes.len() as u32;
3675        extend_with_standard_target_ipv6_entry(&mut entries_bytes, VERDICT_DROP);
3676
3677        // Entry 10: end of input.
3678        extend_with_error_target_ipv6_entry(&mut entries_bytes, TARGET_ERROR);
3679
3680        let mut bytes = ip6t_replace {
3681            name: string_to_ascii_buffer("nat").unwrap(),
3682            num_entries: 11,
3683            size: entries_bytes.len() as u32,
3684            valid_hooks: NfIpHooks::NAT.bits(),
3685            hook_entry: [
3686                prerouting_hook_entry,
3687                input_hook_entry,
3688                0,
3689                output_hook_entry,
3690                postrouting_hook_entry,
3691            ],
3692            underflow: [
3693                prerouting_underflow,
3694                input_underflow,
3695                0,
3696                output_underflow,
3697                postrouting_underflow,
3698            ],
3699            ..Default::default()
3700        }
3701        .as_bytes()
3702        .to_owned();
3703
3704        bytes.extend(entries_bytes);
3705        bytes
3706    }
3707
3708    struct TestIpTablesContext {
3709        registered_ebpf_program: bool,
3710    }
3711
3712    impl TestIpTablesContext {
3713        fn new() -> Self {
3714            Self { registered_ebpf_program: false }
3715        }
3716    }
3717
3718    impl IptReplaceContext for TestIpTablesContext {
3719        fn resolve_ebpf_socket_filter(
3720            &mut self,
3721            path: &BString,
3722        ) -> Result<febpf::ProgramId, IpTableParseError> {
3723            if path == TEST_EBPF_PROGRAM_PATH {
3724                self.registered_ebpf_program = true;
3725                return Ok(TEST_EBPF_PROGRAM_ID);
3726            }
3727            Err(IpTableParseError::InvalidEbpfProgramPath { path: path.clone() })
3728        }
3729    }
3730
3731    #[test_case(
3732        ipv4_table_with_redirect_and_tproxy(),
3733        IpTable::from_ipt_replace,
3734        nat_namespace_v4(),
3735        IPV4_ADDR;
3736        "ipv4"
3737    )]
3738    #[test_case(
3739        ipv6_table_with_redirect_and_tproxy(),
3740        IpTable::from_ip6t_replace,
3741        nat_namespace_v6(),
3742        IPV6_ADDR;
3743        "ipv6"
3744    )]
3745    fn parse_redirect_tproxy_test(
3746        table_bytes: Vec<u8>,
3747        parse_table: fn(&mut TestIpTablesContext, Vec<u8>) -> Result<IpTable, IpTableParseError>,
3748        expected_namespace: fnet_filter_ext::Namespace,
3749        expected_ip_addr: fnet::IpAddress,
3750    ) {
3751        let mut context = TestIpTablesContext::new();
3752        let table = parse_table(&mut context, table_bytes).unwrap();
3753        assert_eq!(table.namespace, expected_namespace);
3754
3755        assert_eq!(
3756            table.routines,
3757            [
3758                nat_prerouting_routine(&expected_namespace),
3759                nat_input_routine(&expected_namespace),
3760                nat_output_routine(&expected_namespace),
3761                nat_postrouting_routine(&expected_namespace),
3762            ]
3763        );
3764
3765        let expected_rules = [
3766            fnet_filter_ext::Rule {
3767                id: fnet_filter_ext::RuleId {
3768                    index: 0,
3769                    routine: nat_prerouting_routine(&expected_namespace).id.clone(),
3770                },
3771                matchers: fnet_filter_ext::Matchers {
3772                    transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
3773                        src_port: None,
3774                        dst_port: None,
3775                    }),
3776                    ..Default::default()
3777                },
3778                action: fnet_filter_ext::Action::TransparentProxy(
3779                    fnet_filter_ext::TransparentProxy::LocalAddr(expected_ip_addr),
3780                ),
3781            },
3782            fnet_filter_ext::Rule {
3783                id: fnet_filter_ext::RuleId {
3784                    index: 1,
3785                    routine: nat_prerouting_routine(&expected_namespace).id.clone(),
3786                },
3787                matchers: fnet_filter_ext::Matchers {
3788                    transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Udp {
3789                        src_port: None,
3790                        dst_port: None,
3791                    }),
3792                    ..Default::default()
3793                },
3794                action: fnet_filter_ext::Action::TransparentProxy(
3795                    fnet_filter_ext::TransparentProxy::LocalPort(NONZERO_PORT),
3796                ),
3797            },
3798            fnet_filter_ext::Rule {
3799                id: fnet_filter_ext::RuleId {
3800                    index: 2,
3801                    routine: nat_prerouting_routine(&expected_namespace).id.clone(),
3802                },
3803                matchers: fnet_filter_ext::Matchers {
3804                    transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
3805                        src_port: None,
3806                        dst_port: None,
3807                    }),
3808                    ..Default::default()
3809                },
3810                action: fnet_filter_ext::Action::TransparentProxy(
3811                    fnet_filter_ext::TransparentProxy::LocalAddrAndPort(
3812                        expected_ip_addr,
3813                        NONZERO_PORT,
3814                    ),
3815                ),
3816            },
3817            fnet_filter_ext::Rule {
3818                id: fnet_filter_ext::RuleId {
3819                    index: 3,
3820                    routine: nat_prerouting_routine(&expected_namespace).id.clone(),
3821                },
3822                matchers: fnet_filter_ext::Matchers::default(),
3823                action: fnet_filter_ext::Action::Accept,
3824            },
3825            fnet_filter_ext::Rule {
3826                id: fnet_filter_ext::RuleId {
3827                    index: 0,
3828                    routine: nat_input_routine(&expected_namespace).id.clone(),
3829                },
3830                matchers: fnet_filter_ext::Matchers::default(),
3831                action: fnet_filter_ext::Action::Accept,
3832            },
3833            fnet_filter_ext::Rule {
3834                id: fnet_filter_ext::RuleId {
3835                    index: 0,
3836                    routine: nat_output_routine(&expected_namespace).id.clone(),
3837                },
3838                matchers: fnet_filter_ext::Matchers {
3839                    transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
3840                        src_port: None,
3841                        dst_port: None,
3842                    }),
3843                    ..Default::default()
3844                },
3845                action: fnet_filter_ext::Action::Redirect { dst_port: None },
3846            },
3847            fnet_filter_ext::Rule {
3848                id: fnet_filter_ext::RuleId {
3849                    index: 1,
3850                    routine: nat_output_routine(&expected_namespace).id.clone(),
3851                },
3852                matchers: fnet_filter_ext::Matchers {
3853                    transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Udp {
3854                        src_port: None,
3855                        dst_port: None,
3856                    }),
3857                    ..Default::default()
3858                },
3859                action: fnet_filter_ext::Action::Redirect {
3860                    dst_port: Some(fnet_filter_ext::PortRange(NONZERO_PORT..=NONZERO_PORT)),
3861                },
3862            },
3863            fnet_filter_ext::Rule {
3864                id: fnet_filter_ext::RuleId {
3865                    index: 2,
3866                    routine: nat_output_routine(&expected_namespace).id.clone(),
3867                },
3868                matchers: fnet_filter_ext::Matchers {
3869                    transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
3870                        src_port: None,
3871                        dst_port: None,
3872                    }),
3873                    ..Default::default()
3874                },
3875                action: fnet_filter_ext::Action::Redirect {
3876                    dst_port: Some(fnet_filter_ext::PortRange(NONZERO_PORT_RANGE)),
3877                },
3878            },
3879            fnet_filter_ext::Rule {
3880                id: fnet_filter_ext::RuleId {
3881                    index: 3,
3882                    routine: nat_output_routine(&expected_namespace).id.clone(),
3883                },
3884                matchers: fnet_filter_ext::Matchers::default(),
3885                action: fnet_filter_ext::Action::Accept,
3886            },
3887            fnet_filter_ext::Rule {
3888                id: fnet_filter_ext::RuleId {
3889                    index: 0,
3890                    routine: nat_postrouting_routine(&expected_namespace).id.clone(),
3891                },
3892                matchers: fnet_filter_ext::Matchers::default(),
3893                action: fnet_filter_ext::Action::Drop,
3894            },
3895        ];
3896
3897        for (rule, expected) in table.rules.iter().zip_eq(expected_rules.into_iter()) {
3898            assert_eq!(rule, &expected);
3899        }
3900    }
3901
3902    fn ip_table_with_mark(
3903        xt_entry: &[u8],
3904        extend_with_standard_target: fn(&mut Vec<u8>, i32),
3905        extend_with_error_target: fn(&mut Vec<u8>, &str),
3906    ) -> Vec<u8> {
3907        let mut entries_bytes = Vec::new();
3908
3909        // Start of PREROUTING built-in chain.
3910        let prerouting_hook_entry = entries_bytes.len() as u32;
3911
3912        // Entry 1: policy of PREROUTING chain.
3913        // Note: PREROUTING chain has no other rules.
3914        let prerouting_underflow = entries_bytes.len() as u32;
3915        extend_with_standard_target(&mut entries_bytes, VERDICT_ACCEPT);
3916
3917        // Start of INPUT built-in chain.
3918        let input_hook_entry = entries_bytes.len() as u32;
3919
3920        // Entry 2: The mark target.
3921        entries_bytes.extend_from_slice(xt_entry);
3922        entries_bytes.extend_from_slice(
3923            xt_entry_target {
3924                target_size: u16::try_from(
3925                    size_of::<xt_entry_target>() + size_of::<xt_mark_tginfo2>(),
3926                )
3927                .unwrap(),
3928                name: string_to_ascii_buffer(TARGET_MARK).unwrap(),
3929                revision: 2,
3930            }
3931            .as_bytes(),
3932        );
3933        entries_bytes.extend_from_slice(xt_mark_tginfo2 { mark: MARK, mask: MASK }.as_bytes());
3934
3935        // Entry 3: policy of INPUT chain.
3936        let input_underflow = entries_bytes.len() as u32;
3937        extend_with_standard_target(&mut entries_bytes, VERDICT_ACCEPT);
3938
3939        // Start of FORWARD built-in chain.
3940        let forward_hook_entry = entries_bytes.len() as u32;
3941
3942        // Entry 4: policy of FORWARD chain.
3943        // Note: FORWARD chain has no other rules.
3944        let forward_underflow = entries_bytes.len() as u32;
3945        extend_with_standard_target(&mut entries_bytes, VERDICT_ACCEPT);
3946
3947        // Start of OUTPUT built-in chain.
3948        let output_hook_entry = entries_bytes.len() as u32;
3949
3950        // Entry 5: policy of OUTPUT chain.
3951        // Note: OUTPUT chain has no other rules.
3952        let output_underflow = entries_bytes.len() as u32;
3953        extend_with_standard_target(&mut entries_bytes, VERDICT_ACCEPT);
3954
3955        // Start of POSTROUTING built-in chain.
3956        let postrouting_hook_entry = entries_bytes.len() as u32;
3957
3958        // Entry 6: policy of POSTROUTING chain.
3959        // Note: POSTROUTING chain has no other rules.
3960        let postrouting_underflow = entries_bytes.len() as u32;
3961        extend_with_standard_target(&mut entries_bytes, VERDICT_ACCEPT);
3962
3963        // Entry 7: end of input.
3964        extend_with_error_target(&mut entries_bytes, TARGET_ERROR);
3965
3966        assert_eq!(IPT_REPLACE_SIZE, IP6T_REPLACE_SIZE);
3967        let mut bytes = ipt_replace {
3968            name: string_to_ascii_buffer("mangle").unwrap(),
3969            num_entries: 7,
3970            size: entries_bytes.len() as u32,
3971            valid_hooks: NfIpHooks::MANGLE.bits(),
3972            hook_entry: [
3973                prerouting_hook_entry,
3974                input_hook_entry,
3975                forward_hook_entry,
3976                output_hook_entry,
3977                postrouting_hook_entry,
3978            ],
3979            underflow: [
3980                prerouting_underflow,
3981                input_underflow,
3982                forward_underflow,
3983                output_underflow,
3984                postrouting_underflow,
3985            ],
3986            ..Default::default()
3987        }
3988        .as_bytes()
3989        .to_owned();
3990
3991        bytes.extend(entries_bytes);
3992        bytes
3993    }
3994
3995    #[test_case(
3996        IpTable::from_ipt_replace,
3997        ipt_entry {
3998            target_offset: u16::try_from(size_of::<ipt_entry>()).unwrap(),
3999            next_offset: u16::try_from(
4000                size_of::<ipt_entry>()
4001                    + size_of::<xt_entry_target>()
4002                    + size_of::<xt_mark_tginfo2>(),
4003            )
4004            .unwrap(),
4005            ..Default::default()
4006        }
4007        .as_bytes(),
4008        extend_with_standard_target_ipv4_entry,
4009        extend_with_error_target_ipv4_entry,
4010        mangle_namespace_v4(); "ipv4"
4011    )]
4012    #[test_case(
4013        IpTable::from_ip6t_replace,
4014        ip6t_entry {
4015            target_offset: u16::try_from(size_of::<ip6t_entry>()).unwrap(),
4016            next_offset: u16::try_from(
4017                size_of::<ip6t_entry>()
4018                    + size_of::<xt_entry_target>()
4019                    + size_of::<xt_mark_tginfo2>(),
4020            )
4021            .unwrap(),
4022            ..Default::default()
4023        }
4024        .as_bytes(),
4025        extend_with_standard_target_ipv6_entry,
4026        extend_with_error_target_ipv6_entry,
4027        mangle_namespace_v6(); "ipv6"
4028    )]
4029    fn parse_mark_test(
4030        parse_fn: fn(&mut TestIpTablesContext, Vec<u8>) -> Result<IpTable, IpTableParseError>,
4031        xt_entry: &[u8],
4032        extend_with_standard_target: fn(&mut Vec<u8>, i32),
4033        extend_with_error_target: fn(&mut Vec<u8>, &str),
4034        expected_namespace: fnet_filter_ext::Namespace,
4035    ) {
4036        let mut context = TestIpTablesContext::new();
4037        let table = parse_fn(
4038            &mut context,
4039            ip_table_with_mark(xt_entry, extend_with_standard_target, extend_with_error_target),
4040        )
4041        .unwrap();
4042
4043        assert_eq!(table.namespace, expected_namespace);
4044
4045        assert_eq!(
4046            table.routines,
4047            [
4048                mangle_prerouting_routine(&expected_namespace),
4049                mangle_input_routine(&expected_namespace),
4050                mangle_forward_routine(&expected_namespace),
4051                mangle_output_routine(&expected_namespace),
4052                mangle_postrouting_routine(&expected_namespace),
4053            ]
4054        );
4055
4056        let expected_rules = [
4057            fnet_filter_ext::Rule {
4058                id: fnet_filter_ext::RuleId {
4059                    index: 0,
4060                    routine: mangle_prerouting_routine(&expected_namespace).id.clone(),
4061                },
4062                matchers: fnet_filter_ext::Matchers::default(),
4063                action: fnet_filter_ext::Action::Accept,
4064            },
4065            fnet_filter_ext::Rule {
4066                id: fnet_filter_ext::RuleId {
4067                    index: 0,
4068                    routine: mangle_input_routine(&expected_namespace).id.clone(),
4069                },
4070                matchers: fnet_filter_ext::Matchers::default(),
4071                action: fnet_filter_ext::Action::Mark {
4072                    domain: fnet::MarkDomain::Mark1,
4073                    action: fnet_filter_ext::MarkAction::SetMark {
4074                        clearing_mask: MASK,
4075                        mark: MARK,
4076                    },
4077                },
4078            },
4079            fnet_filter_ext::Rule {
4080                id: fnet_filter_ext::RuleId {
4081                    index: 1,
4082                    routine: mangle_input_routine(&expected_namespace).id.clone(),
4083                },
4084                matchers: fnet_filter_ext::Matchers::default(),
4085                action: fnet_filter_ext::Action::Accept,
4086            },
4087            fnet_filter_ext::Rule {
4088                id: fnet_filter_ext::RuleId {
4089                    index: 0,
4090                    routine: mangle_forward_routine(&expected_namespace).id.clone(),
4091                },
4092                matchers: fnet_filter_ext::Matchers::default(),
4093                action: fnet_filter_ext::Action::Accept,
4094            },
4095            fnet_filter_ext::Rule {
4096                id: fnet_filter_ext::RuleId {
4097                    index: 0,
4098                    routine: mangle_output_routine(&expected_namespace).id.clone(),
4099                },
4100                matchers: fnet_filter_ext::Matchers::default(),
4101                action: fnet_filter_ext::Action::Accept,
4102            },
4103            fnet_filter_ext::Rule {
4104                id: fnet_filter_ext::RuleId {
4105                    index: 0,
4106                    routine: mangle_postrouting_routine(&expected_namespace).id.clone(),
4107                },
4108                matchers: fnet_filter_ext::Matchers::default(),
4109                action: fnet_filter_ext::Action::Accept,
4110            },
4111        ];
4112
4113        for (rule, expected) in table.rules.iter().zip_eq(expected_rules.into_iter()) {
4114            assert_eq!(rule, &expected);
4115        }
4116    }
4117
4118    fn table_with_wrong_size() -> Vec<u8> {
4119        let mut bytes = Vec::new();
4120
4121        bytes.extend_from_slice(
4122            ipt_replace {
4123                name: string_to_ascii_buffer("filter").unwrap(),
4124                num_entries: 1,
4125                size: 0,
4126                ..Default::default()
4127            }
4128            .as_bytes(),
4129        );
4130
4131        extend_with_error_target_ipv4_entry(&mut bytes, TARGET_ERROR);
4132        bytes
4133    }
4134
4135    fn table_with_wrong_num_entries() -> Vec<u8> {
4136        let mut entries_bytes = Vec::new();
4137
4138        extend_with_error_target_ipv4_entry(&mut entries_bytes, TARGET_ERROR);
4139
4140        let mut bytes = ipt_replace {
4141            name: string_to_ascii_buffer("filter").unwrap(),
4142            num_entries: 3,
4143            size: entries_bytes.len() as u32,
4144            ..Default::default()
4145        }
4146        .as_bytes()
4147        .to_owned();
4148        bytes.extend(entries_bytes);
4149        bytes
4150    }
4151
4152    fn table_with_no_entries() -> Vec<u8> {
4153        ipt_replace {
4154            name: string_to_ascii_buffer("filter").unwrap(),
4155            num_entries: 0,
4156            size: 0,
4157            ..Default::default()
4158        }
4159        .as_bytes()
4160        .to_owned()
4161    }
4162
4163    fn table_with_invalid_hook_bits() -> Vec<u8> {
4164        let mut entries_bytes = Vec::new();
4165
4166        // Entry 0: end of input.
4167        extend_with_error_target_ipv4_entry(&mut entries_bytes, TARGET_ERROR);
4168
4169        let mut bytes = ipt_replace {
4170            name: string_to_ascii_buffer("filter").unwrap(),
4171            num_entries: 1,
4172            size: entries_bytes.len() as u32,
4173            valid_hooks: 0b00011,
4174            ..Default::default()
4175        }
4176        .as_bytes()
4177        .to_owned();
4178
4179        bytes.extend(entries_bytes);
4180        bytes
4181    }
4182
4183    fn table_with_invalid_hook_entry() -> Vec<u8> {
4184        let mut entries_bytes = Vec::new();
4185
4186        // Entry 0: end of input.
4187        extend_with_error_target_ipv4_entry(&mut entries_bytes, TARGET_ERROR);
4188
4189        let mut bytes = ipt_replace {
4190            name: string_to_ascii_buffer("filter").unwrap(),
4191            num_entries: 1,
4192            size: entries_bytes.len() as u32,
4193            valid_hooks: NfIpHooks::FILTER.bits(),
4194
4195            // 8 does not refer to a valid entry.
4196            hook_entry: [0, 8, 0, 0, 0],
4197            underflow: [0, 8, 0, 0, 0],
4198            ..Default::default()
4199        }
4200        .as_bytes()
4201        .to_owned();
4202
4203        bytes.extend(entries_bytes);
4204        bytes
4205    }
4206
4207    fn table_with_hook_ranges_overlap() -> Vec<u8> {
4208        let mut entries_bytes = Vec::new();
4209
4210        // Entry 0: policy of INPUT, FORWARD, OUTPUT.
4211        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
4212
4213        // Entry 1: end of input.
4214        extend_with_error_target_ipv4_entry(&mut entries_bytes, "ERROR");
4215
4216        let mut bytes = ipt_replace {
4217            name: string_to_ascii_buffer("filter").unwrap(),
4218            num_entries: 2,
4219            size: entries_bytes.len() as u32,
4220            valid_hooks: NfIpHooks::FILTER.bits(),
4221            hook_entry: [0, 0, 0, 0, 0],
4222            underflow: [0, 0, 0, 0, 0],
4223            ..Default::default()
4224        }
4225        .as_bytes()
4226        .to_owned();
4227
4228        bytes.extend(entries_bytes);
4229        bytes
4230    }
4231
4232    fn table_with_unexpected_error_target() -> Vec<u8> {
4233        let mut entries_bytes = Vec::new();
4234
4235        // Entry 0: start of "mychain".
4236        // From hook_entry, this should contain rules of the INPUT chain.
4237        extend_with_error_target_ipv4_entry(&mut entries_bytes, "mychain");
4238
4239        // Entry 1: policy of FORWARD.
4240        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
4241
4242        // Entry 2: policy of OUTPUT.
4243        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
4244
4245        // Entry 3: end of input.
4246        extend_with_error_target_ipv4_entry(&mut entries_bytes, TARGET_ERROR);
4247
4248        let mut bytes = ipt_replace {
4249            name: string_to_ascii_buffer("filter").unwrap(),
4250            num_entries: 4,
4251            size: entries_bytes.len() as u32,
4252            valid_hooks: NfIpHooks::FILTER.bits(),
4253            hook_entry: [0, 0, 176, 328, 0],
4254            underflow: [0, 0, 176, 328, 0],
4255            ..Default::default()
4256        }
4257        .as_bytes()
4258        .to_owned();
4259
4260        bytes.extend(entries_bytes);
4261        bytes
4262    }
4263
4264    fn table_with_chain_with_no_policy() -> Vec<u8> {
4265        let mut entries_bytes = Vec::new();
4266
4267        // Entry 0: policy of INPUT.
4268        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
4269
4270        // Entry 1: policy of FORWARD.
4271        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
4272
4273        // Entry 2: policy of OUTPUT.
4274        extend_with_standard_target_ipv4_entry(&mut entries_bytes, VERDICT_ACCEPT);
4275
4276        // Entry 3: start of "mychain".
4277        extend_with_error_target_ipv4_entry(&mut entries_bytes, "mychain");
4278
4279        // Entry 4: end of input.
4280        extend_with_error_target_ipv4_entry(&mut entries_bytes, TARGET_ERROR);
4281
4282        let mut bytes = ipt_replace {
4283            name: string_to_ascii_buffer("filter").unwrap(),
4284            num_entries: 5,
4285            size: entries_bytes.len() as u32,
4286            valid_hooks: NfIpHooks::FILTER.bits(),
4287            hook_entry: [0, 0, 152, 304, 0],
4288            underflow: [0, 0, 152, 304, 0],
4289            ..Default::default()
4290        }
4291        .as_bytes()
4292        .to_owned();
4293
4294        bytes.extend(entries_bytes);
4295        bytes
4296    }
4297
4298    #[test_case(
4299        table_with_wrong_size(),
4300        IpTableParseError::SizeMismatch { specified_size: 0, entries_size: 176 };
4301        "wrong size"
4302    )]
4303    #[test_case(
4304        table_with_wrong_num_entries(),
4305        IpTableParseError::NumEntriesMismatch { specified: 3, found: 1 };
4306        "wrong number of entries"
4307    )]
4308    #[test_case(
4309        table_with_no_entries(),
4310        IpTableParseError::NoTrailingErrorTarget;
4311        "no trailing error target"
4312    )]
4313    #[test_case(
4314        table_with_invalid_hook_bits(),
4315        IpTableParseError::InvalidHooksForTable { hooks: 3, table_name: TABLE_FILTER };
4316        "invalid hook bits"
4317    )]
4318    #[test_case(
4319        table_with_invalid_hook_entry(),
4320        IpTableParseError::InvalidHookEntryOrUnderflow { index: 1, start: 8, end: 8 };
4321        "hook does not point to entry"
4322    )]
4323    #[test_case(
4324        table_with_hook_ranges_overlap(),
4325        IpTableParseError::HookRangesOverlap { offset: 0 };
4326        "hook ranges overlap"
4327    )]
4328    #[test_case(
4329        table_with_unexpected_error_target(),
4330        IpTableParseError::UnexpectedErrorTarget { error_name: "mychain".to_owned() };
4331        "unexpected error target"
4332    )]
4333    #[test_case(
4334        table_with_chain_with_no_policy(),
4335        IpTableParseError::ChainHasNoPolicy { chain_name: String::from("mychain") };
4336        "chain with no policy"
4337    )]
4338    fn parse_table_error(bytes: Vec<u8>, expected_error: IpTableParseError) {
4339        let mut context = TestIpTablesContext::new();
4340        assert_eq!(IpTable::from_ipt_replace(&mut context, bytes).unwrap_err(), expected_error);
4341    }
4342
4343    #[test_case(&[], Err(AsciiConversionError::NulByteNotFound { chars: vec![] }); "empty slice")]
4344    #[test_case(&[0], Ok(String::from("")); "size 1 slice")]
4345    #[test_case(
4346        &[102, 105, 108, 116, 101, 114, 0],
4347        Ok(String::from("filter"));
4348        "valid string with trailing nul byte"
4349    )]
4350    #[test_case(
4351        &[102, 105, 108, 116, 101, 114, 0, 0, 0, 0],
4352        Ok(String::from("filter"));
4353        "multiple trailing nul bytes"
4354    )]
4355    #[test_case(&[0; 8], Ok(String::from("")); "empty string")]
4356    #[test_case(&[0, 88, 88, 88, 88, 88], Ok(String::from("")); "ignores chars after nul byte")]
4357    fn ascii_to_string_test(input: &[c_char], expected: Result<String, AsciiConversionError>) {
4358        assert_eq!(ascii_to_string(input), expected);
4359    }
4360
4361    #[fuchsia::test]
4362    fn ascii_to_string_non_ascii_test() {
4363        #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
4364        {
4365            let invalid_bytes: [c_char; 4] = [159, 146, 150, 0];
4366            assert_eq!(ascii_to_string(&invalid_bytes), Err(AsciiConversionError::NonAsciiChar));
4367        }
4368        #[cfg(target_arch = "x86_64")]
4369        {
4370            let invalid_bytes: [c_char; 4] = [-97, -110, -106, 0];
4371            assert_eq!(ascii_to_string(&invalid_bytes), Err(AsciiConversionError::NonAsciiChar));
4372        }
4373    }
4374
4375    #[test_case("", Ok([0, 0, 0, 0, 0, 0, 0, 0]); "empty string")]
4376    #[test_case("filter", Ok([102, 105, 108, 116, 101, 114, 0, 0]); "valid string")]
4377    #[test_case(
4378        "very long string",
4379        Err(AsciiConversionError::BufferTooSmall { buffer_size: 8, data_size: 17 });
4380        "string does not fit"
4381    )]
4382    #[test_case(
4383        "\u{211D}",
4384        Err(AsciiConversionError::NonAsciiChar);
4385        "non-ASCII character"
4386    )]
4387    fn string_to_8_char_buffer_test(
4388        input: &str,
4389        output: Result<[c_char; 8], AsciiConversionError>,
4390    ) {
4391        assert_eq!(string_to_ascii_buffer::<8>(input), output);
4392    }
4393
4394    #[test_case( [0, 0, 0, 0], [0x0, 0x0, 0x0, 0x0], Ok(None); "unset")]
4395    #[test_case(
4396        [127, 0, 0, 1],
4397        [0xff, 0xff, 0xff, 0xff],
4398        Ok(Some(fidl_subnet!("127.0.0.1/32")));
4399        "full address"
4400    )]
4401    #[test_case(
4402        [127, 0, 0, 1],
4403        [0xff, 0x0, 0x0, 0xff],
4404        Err(IpAddressConversionError::IpV4SubnetMaskHasNonPrefixBits { mask: 4278190335 });
4405        "invalid mask"
4406    )]
4407    #[test_case(
4408        [192, 0, 2, 15],
4409        [0xff, 0xff, 0xff, 0x0],
4410        Ok(Some(fidl_subnet!("192.0.2.15/24")));
4411        "subnet"
4412    )]
4413    fn ipv4_address_test(
4414        be_addr: [u8; 4],
4415        be_mask: [u8; 4],
4416        expected: Result<Option<fnet::Subnet>, IpAddressConversionError>,
4417    ) {
4418        assert_eq!(
4419            ipv4_to_subnet(
4420                in_addr { s_addr: u32::from_be_bytes(be_addr).to_be() },
4421                in_addr { s_addr: u32::from_be_bytes(be_mask).to_be() },
4422            ),
4423            expected
4424        );
4425    }
4426
4427    #[test_case(
4428        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
4429        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
4430        Ok(None);
4431        "unset"
4432    )]
4433    #[test_case(
4434        [0xff, 0x06, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xc3],
4435        [
4436            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
4437            0xff, 0xff
4438        ],
4439        Ok(Some(fidl_subnet!("ff06::c3/128")));
4440        "full address"
4441    )]
4442    #[test_case(
4443        [0xff, 0x06, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xc3],
4444        [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0],
4445        Ok(Some(fidl_subnet!("ff06::c3/96")));
4446        "subnet"
4447    )]
4448    #[test_case(
4449        [0xff, 0x06, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xc3],
4450        [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0xf],
4451        Err(IpAddressConversionError::IpV6SubnetMaskHasNonPrefixBits {
4452            mask: 340282366920938463463374607427473244175,
4453        });
4454        "invalid mask"
4455    )]
4456    fn ipv6_address_test(
4457        be_addr: [u8; 16],
4458        be_mask: [u8; 16],
4459        expected: Result<Option<fnet::Subnet>, IpAddressConversionError>,
4460    ) {
4461        assert_eq!(
4462            ipv6_to_subnet(
4463                in6_addr { in6_u: in6_addr__bindgen_ty_1 { u6_addr8: be_addr } },
4464                in6_addr { in6_u: in6_addr__bindgen_ty_1 { u6_addr8: be_mask } },
4465            ),
4466            expected
4467        );
4468    }
4469}