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