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