Skip to main content

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