1use crate::bpf::fs::resolve_pinned_bpf_object;
6use crate::bpf::program::{Program, ProgramHandle};
7use crate::security;
8use crate::task::CurrentTask;
9use crate::vfs::socket::iptables_utils::{
10 self, Ip, IpTableParseError, IptReplaceContext, TableId, string_to_ascii_buffer,
11};
12use crate::vfs::socket::{SockOptValue, SocketDomain, SocketHandle, SocketType};
13use bstr::BString;
14use fidl_fuchsia_net_filter_ext::sync::Controller;
15use fidl_fuchsia_net_filter_ext::{
16 Change, CommitError, ControllerId, PushChangesError, RegisterEbpfProgramError,
17};
18use fuchsia_component::client::connect_to_protocol_sync;
19use itertools::Itertools;
20use starnix_logging::{log_error, log_warn, track_stub};
21use starnix_sync::{KernelIpTables, Locked, OrderedRwLock, Unlocked};
22use starnix_uapi::auth::CAP_NET_ADMIN;
23use starnix_uapi::errors::Errno;
24use starnix_uapi::iptables_flags::NfIpHooks;
25use starnix_uapi::open_flags::OpenFlags;
26use starnix_uapi::{
27 IP6T_SO_GET_ENTRIES, IP6T_SO_GET_INFO, IP6T_SO_GET_REVISION_MATCH, IP6T_SO_GET_REVISION_TARGET,
28 IPT_SO_GET_ENTRIES, IPT_SO_GET_INFO, IPT_SO_GET_REVISION_MATCH, IPT_SO_GET_REVISION_TARGET,
29 IPT_SO_SET_ADD_COUNTERS, IPT_SO_SET_REPLACE, SOL_IP, SOL_IPV6, errno, error, ip6t_entry,
30 ip6t_get_entries, ip6t_getinfo, ipt_entry, ipt_get_entries, ipt_getinfo,
31 nf_inet_hooks_NF_INET_NUMHOOKS, xt_counters, xt_counters_info,
32 xt_entry_target__bindgen_ty_1__bindgen_ty_1 as xt_entry_target, xt_error_target,
33 xt_get_revision, xt_standard_target,
34};
35use static_assertions::const_assert_eq;
36use std::collections::HashMap;
37use std::mem::size_of;
38use std::ops::{Deref as _, Index, IndexMut};
39use zerocopy::{FromBytes, IntoBytes};
40use {fidl_fuchsia_ebpf as febpf, fidl_fuchsia_net_filter as fnet_filter};
41
42const NAMESPACE_ID_PREFIX: &str = "starnix";
43
44const IPT_ENTRY_SIZE: u16 = size_of::<ipt_entry>() as u16;
45const IP6T_ENTRY_SIZE: u16 = size_of::<ip6t_entry>() as u16;
46const STANDARD_TARGET_SIZE: u16 = size_of::<xt_standard_target>() as u16;
47const ERROR_TARGET_SIZE: u16 = size_of::<xt_error_target>() as u16;
48
49const FILTER_HOOKS: [u32; 5] = [0, 0, 1, 2, 0];
57const NAT_HOOKS: [u32; 5] = [0, 1, 0, 2, 3];
58const MANGLE_HOOKS: [u32; 5] = [0, 1, 2, 3, 4];
59const RAW_HOOKS: [u32; 5] = [0, 0, 0, 1, 0];
60
61#[derive(Debug, Default)]
64struct IpTable {
65 pub valid_hooks: u32,
66 pub hook_entry: [u32; nf_inet_hooks_NF_INET_NUMHOOKS as usize],
67 pub underflow: [u32; nf_inet_hooks_NF_INET_NUMHOOKS as usize],
68 pub num_entries: u32,
69 pub size: u32,
70 pub entries: Vec<u8>,
71 pub num_counters: u32,
72 pub counters: Vec<xt_counters>,
73}
74
75impl IpTable {
76 fn accept_policy_v4() -> Vec<u8> {
77 [
78 ipt_entry {
79 target_offset: IPT_ENTRY_SIZE,
80 next_offset: IPT_ENTRY_SIZE + STANDARD_TARGET_SIZE,
81 ..Default::default()
82 }
83 .as_bytes(),
84 xt_entry_target { target_size: STANDARD_TARGET_SIZE, ..Default::default() }.as_bytes(),
85 iptables_utils::VerdictWithPadding {
86 verdict: iptables_utils::VERDICT_ACCEPT,
87 ..Default::default()
88 }
89 .as_bytes(),
90 ]
91 .concat()
92 }
93
94 fn accept_policy_v6() -> Vec<u8> {
95 [
96 ip6t_entry {
97 target_offset: IP6T_ENTRY_SIZE,
98 next_offset: IP6T_ENTRY_SIZE + STANDARD_TARGET_SIZE,
99 ..Default::default()
100 }
101 .as_bytes(),
102 xt_entry_target { target_size: STANDARD_TARGET_SIZE, ..Default::default() }.as_bytes(),
103 iptables_utils::VerdictWithPadding {
104 verdict: iptables_utils::VERDICT_ACCEPT,
105 ..Default::default()
106 }
107 .as_bytes(),
108 ]
109 .concat()
110 }
111
112 fn end_of_input_v4() -> Vec<u8> {
113 [
114 ipt_entry {
115 target_offset: IPT_ENTRY_SIZE,
116 next_offset: IPT_ENTRY_SIZE + ERROR_TARGET_SIZE,
117 ..Default::default()
118 }
119 .as_bytes(),
120 xt_entry_target {
121 target_size: ERROR_TARGET_SIZE,
122 name: string_to_ascii_buffer("ERROR").expect("convert \"ERROR\" to ASCII"),
123 revision: 0,
124 }
125 .as_bytes(),
126 iptables_utils::ErrorNameWithPadding {
127 errorname: string_to_ascii_buffer("ERROR").expect("convert \"ERROR\" to ASCII"),
128 ..Default::default()
129 }
130 .as_bytes(),
131 ]
132 .concat()
133 }
134
135 fn end_of_input_v6() -> Vec<u8> {
136 [
137 ip6t_entry {
138 target_offset: IP6T_ENTRY_SIZE,
139 next_offset: IP6T_ENTRY_SIZE + ERROR_TARGET_SIZE,
140 ..Default::default()
141 }
142 .as_bytes(),
143 xt_entry_target {
144 target_size: ERROR_TARGET_SIZE,
145 name: string_to_ascii_buffer("ERROR").expect("convert \"ERROR\" to ASCII"),
146 revision: 0,
147 }
148 .as_bytes(),
149 iptables_utils::ErrorNameWithPadding {
150 errorname: string_to_ascii_buffer("ERROR").expect("convert \"ERROR\" to ASCII"),
151 ..Default::default()
152 }
153 .as_bytes(),
154 ]
155 .concat()
156 }
157
158 fn default_ipv4_nat_table() -> Self {
159 let hook_entry = NAT_HOOKS.map(|n| n * u32::from(IPT_ENTRY_SIZE + STANDARD_TARGET_SIZE));
160 let accept_policy = Self::accept_policy_v4();
161 let entries = [
162 accept_policy.as_slice(),
163 accept_policy.as_slice(),
164 accept_policy.as_slice(),
165 accept_policy.as_slice(),
166 Self::end_of_input_v4().as_slice(),
167 ]
168 .concat();
169 Self {
170 valid_hooks: NfIpHooks::NAT.bits(),
171 hook_entry,
172 underflow: hook_entry,
173 num_entries: 5,
174 size: entries.len() as u32,
175 entries,
176 ..Default::default()
177 }
178 }
179
180 fn default_ipv6_nat_table() -> Self {
181 let hook_entry = NAT_HOOKS.map(|n| n * u32::from(IP6T_ENTRY_SIZE + STANDARD_TARGET_SIZE));
182 let accept_policy = Self::accept_policy_v6();
183 let entries = [
184 accept_policy.as_slice(),
185 accept_policy.as_slice(),
186 accept_policy.as_slice(),
187 accept_policy.as_slice(),
188 Self::end_of_input_v6().as_slice(),
189 ]
190 .concat();
191
192 Self {
193 valid_hooks: NfIpHooks::NAT.bits(),
194 hook_entry,
195 underflow: hook_entry,
196 num_entries: 5,
197 size: entries.len() as u32,
198 entries,
199 ..Default::default()
200 }
201 }
202
203 fn default_ipv4_filter_table() -> Self {
204 let hook_entry = FILTER_HOOKS.map(|n| n * u32::from(IPT_ENTRY_SIZE + STANDARD_TARGET_SIZE));
205 let accept_policy = Self::accept_policy_v4();
206 let entries = [
207 accept_policy.as_slice(),
208 accept_policy.as_slice(),
209 accept_policy.as_slice(),
210 Self::end_of_input_v4().as_slice(),
211 ]
212 .concat();
213
214 Self {
215 valid_hooks: NfIpHooks::FILTER.bits(),
216 hook_entry,
217 underflow: hook_entry,
218 num_entries: 4,
219 size: entries.len() as u32,
220 entries,
221 ..Default::default()
222 }
223 }
224
225 fn default_ipv6_filter_table() -> Self {
226 let hook_entry =
227 FILTER_HOOKS.map(|n| n * u32::from(IP6T_ENTRY_SIZE + STANDARD_TARGET_SIZE));
228 let accept_policy = Self::accept_policy_v6();
229 let entries = [
230 accept_policy.as_slice(),
231 accept_policy.as_slice(),
232 accept_policy.as_slice(),
233 Self::end_of_input_v6().as_slice(),
234 ]
235 .concat();
236
237 Self {
238 valid_hooks: NfIpHooks::FILTER.bits(),
239 hook_entry,
240 underflow: hook_entry,
241 num_entries: 4,
242 size: entries.len() as u32,
243 entries,
244 ..Default::default()
245 }
246 }
247
248 fn default_ipv4_mangle_table() -> Self {
249 let hook_entry = MANGLE_HOOKS.map(|n| n * u32::from(IPT_ENTRY_SIZE + STANDARD_TARGET_SIZE));
250 let accept_policy = Self::accept_policy_v4();
251 let entries = [
252 accept_policy.as_slice(),
253 accept_policy.as_slice(),
254 accept_policy.as_slice(),
255 accept_policy.as_slice(),
256 accept_policy.as_slice(),
257 Self::end_of_input_v4().as_slice(),
258 ]
259 .concat();
260
261 Self {
262 valid_hooks: NfIpHooks::MANGLE.bits(),
263 hook_entry,
264 underflow: hook_entry,
265 num_entries: 6,
266 size: entries.len() as u32,
267 entries,
268 ..Default::default()
269 }
270 }
271
272 fn default_ipv6_mangle_table() -> Self {
273 let hook_entry =
274 MANGLE_HOOKS.map(|n| n * u32::from(IP6T_ENTRY_SIZE + STANDARD_TARGET_SIZE));
275 let accept_policy = Self::accept_policy_v6();
276 let entries = [
277 accept_policy.as_slice(),
278 accept_policy.as_slice(),
279 accept_policy.as_slice(),
280 accept_policy.as_slice(),
281 accept_policy.as_slice(),
282 Self::end_of_input_v6().as_slice(),
283 ]
284 .concat();
285
286 Self {
287 valid_hooks: NfIpHooks::MANGLE.bits(),
288 hook_entry,
289 underflow: hook_entry,
290 num_entries: 6,
291 size: entries.len() as u32,
292 entries,
293 ..Default::default()
294 }
295 }
296
297 fn default_ipv4_raw_table() -> Self {
298 let hook_entry = RAW_HOOKS.map(|n| n * u32::from(IPT_ENTRY_SIZE + STANDARD_TARGET_SIZE));
299 let accept_policy = Self::accept_policy_v4();
300 let entries = [
301 accept_policy.as_slice(),
302 accept_policy.as_slice(),
303 Self::end_of_input_v4().as_slice(),
304 ]
305 .concat();
306
307 Self {
308 valid_hooks: NfIpHooks::RAW.bits(),
309 hook_entry,
310 underflow: hook_entry,
311 num_entries: 3,
312 size: entries.len() as u32,
313 entries,
314 ..Default::default()
315 }
316 }
317
318 fn default_ipv6_raw_table() -> Self {
319 let hook_entry = RAW_HOOKS.map(|n| n * u32::from(IP6T_ENTRY_SIZE + STANDARD_TARGET_SIZE));
320 let accept_policy = Self::accept_policy_v6();
321 let entries = [
322 accept_policy.as_slice(),
323 accept_policy.as_slice(),
324 Self::end_of_input_v6().as_slice(),
325 ]
326 .concat();
327
328 Self {
329 valid_hooks: NfIpHooks::RAW.bits(),
330 hook_entry,
331 underflow: hook_entry,
332 num_entries: 3,
333 size: entries.len() as u32,
334 entries,
335 ..Default::default()
336 }
337 }
338}
339
340type IpTablesArray = [IpTable; iptables_utils::NUM_TABLES];
341
342impl Index<TableId> for IpTablesArray {
343 type Output = IpTable;
344
345 fn index(&self, index: TableId) -> &Self::Output {
346 &self[index as usize]
347 }
348}
349
350impl IndexMut<TableId> for IpTablesArray {
351 fn index_mut(&mut self, index: TableId) -> &mut Self::Output {
352 &mut self[index as usize]
353 }
354}
355
356fn default_ipv4_tables() -> IpTablesArray {
357 const_assert_eq!(TableId::Filter as usize, 0);
358 const_assert_eq!(TableId::Mangle as usize, 1);
359 const_assert_eq!(TableId::Nat as usize, 2);
360 const_assert_eq!(TableId::Raw as usize, 3);
361 [
362 IpTable::default_ipv4_filter_table(),
363 IpTable::default_ipv4_mangle_table(),
364 IpTable::default_ipv4_nat_table(),
365 IpTable::default_ipv4_raw_table(),
366 ]
367}
368
369fn default_ipv6_tables() -> IpTablesArray {
370 [
371 IpTable::default_ipv6_filter_table(),
372 IpTable::default_ipv6_mangle_table(),
373 IpTable::default_ipv6_nat_table(),
374 IpTable::default_ipv6_raw_table(),
375 ]
376}
377
378#[derive(Default, PartialEq, Eq, Copy, Clone)]
380struct IpTableSet(u64);
381
382impl IpTableSet {
383 fn element_index(ip: Ip, table: TableId) -> usize {
384 (ip as usize) * iptables_utils::NUM_TABLES + table as usize
385 }
386
387 fn add(&mut self, ip: Ip, table: TableId) {
388 self.0 |= 1 << Self::element_index(ip, table);
389 }
390
391 fn remove(&mut self, ip: Ip, table: TableId) {
392 self.0 &= !(1 << Self::element_index(ip, table));
393 }
394
395 fn is_empty(&self) -> bool {
396 *self == Self::default()
397 }
398}
399
400struct EbpfProgramState {
404 #[allow(dead_code)]
405 program: ProgramHandle,
406 tables: IpTableSet,
407}
408
409#[derive(Default)]
410struct LazyController {
411 controller: Option<Controller>,
412}
413
414impl LazyController {
415 fn get(&mut self) -> Result<&mut Controller, Errno> {
416 if self.controller.is_none() {
417 let control_proxy =
418 connect_to_protocol_sync::<fnet_filter::ControlMarker>().map_err(|e| {
419 log_error!("failed to connect to fuchsia.net.filter.Control: {e}");
420 errno!(EIO)
421 })?;
422 self.controller = Some(
423 Controller::new(
424 &control_proxy,
425 &ControllerId(NAMESPACE_ID_PREFIX.to_string()),
426 zx::MonotonicInstant::INFINITE,
427 )
428 .map_err(|e| {
429 log_error!("failed to create filter controller: {e}");
430 errno!(EIO)
431 })?,
432 );
433 }
434 Ok(self.controller.as_mut().expect("just ensured this is Some"))
435 }
436}
437
438struct IpTablesState {
439 ipv4: IpTablesArray,
440 ipv6: IpTablesArray,
441
442 controller: LazyController,
444
445 ebpf_programs: HashMap<febpf::ProgramId, EbpfProgramState>,
446}
447
448impl IpTablesState {
449 fn send_changes_to_net_filter(
450 &mut self,
451 changes: impl Iterator<Item = Change>,
452 ) -> Result<(), Errno> {
453 let controller = self.controller.get()?;
454 for chunk in &changes.chunks(fnet_filter::MAX_BATCH_SIZE as usize) {
455 match controller.push_changes(chunk.collect(), zx::MonotonicInstant::INFINITE) {
456 Ok(()) => {}
457 Err(
458 e @ (PushChangesError::CallMethod(_)
459 | PushChangesError::TooManyChanges
460 | PushChangesError::FidlConversion(_)),
461 ) => {
462 log_warn!(
463 "IpTables: failed to call \
464 fuchsia.net.filter.NamespaceController/PushChanges: {e}"
465 );
466 return error!(ECOMM);
467 }
468 Err(e @ PushChangesError::ErrorOnChange(_)) => {
469 log_warn!(
470 "IpTables: fuchsia.net.filter.NamespaceController/PushChanges \
471 returned error: {e}"
472 );
473 return error!(EINVAL);
474 }
475 }
476 }
477
478 match controller.commit_idempotent(zx::MonotonicInstant::INFINITE) {
479 Ok(()) => Ok(()),
480 Err(e @ (CommitError::CallMethod(_) | CommitError::FidlConversion(_))) => {
481 log_warn!(
482 "IpTables: failed to call \
483 fuchsia.net.filter.NamespaceController/Commit: {e}"
484 );
485 error!(ECOMM)
486 }
487 Err(
488 e @ (CommitError::RuleWithInvalidMatcher(_)
489 | CommitError::RuleWithInvalidAction(_)
490 | CommitError::TransparentProxyWithInvalidMatcher(_)
491 | CommitError::CyclicalRoutineGraph(_)
492 | CommitError::RedirectWithInvalidMatcher(_)
493 | CommitError::MasqueradeWithInvalidMatcher(_)
494 | CommitError::RejectWithInvalidMatcher(_)
495 | CommitError::ErrorOnChange(_)),
496 ) => {
497 log_warn!(
498 "IpTables: fuchsia.net.filter.NamespaceController/Commit \
499 returned error: {e}"
500 );
501 error!(EINVAL)
502 }
503 }
504 }
505
506 fn register_ebpf_programs(
508 &mut self,
509 ip: Ip,
510 table_id: TableId,
511 ebpf_programs: HashMap<febpf::ProgramId, ProgramHandle>,
512 ) -> Result<(), Errno> {
513 let controller = self.controller.get()?;
515 for (id, program_handle) in &ebpf_programs {
516 if !self.ebpf_programs.contains_key(id) {
517 let program: &Program = program_handle.deref();
518 match controller.register_ebpf_program(
519 program.fidl_handle(),
520 program.try_into()?,
521 zx::MonotonicInstant::INFINITE,
522 ) {
523 Ok(_) => {}
524 Err(RegisterEbpfProgramError::AlreadyRegistered) => {}
525 Err(e) => {
526 log_warn!("IpTables: failed to register eBPF program: {e}");
527 return error!(EINVAL);
528 }
529 }
530 }
531 }
532
533 self.ebpf_programs.retain(|_id, program_state| {
535 program_state.tables.remove(ip, table_id);
536
537 !program_state.tables.is_empty()
538 });
539
540 for (id, program_handle) in &ebpf_programs {
542 let entry = self.ebpf_programs.entry(*id).or_insert_with(|| EbpfProgramState {
543 program: program_handle.clone(),
544 tables: IpTableSet::default(),
545 });
546 entry.tables.add(ip, table_id);
547 }
548
549 Ok(())
550 }
551
552 fn replace_table(
553 &mut self,
554 ip: Ip,
555 ebpf_programs: HashMap<febpf::ProgramId, ProgramHandle>,
556 table: iptables_utils::IpTable,
557 ) -> Result<(), Errno> {
558 let entries = table.parser.entries_bytes().to_vec();
559 let replace_info = table.parser.replace_info.clone();
560 let iptable_entry = IpTable {
561 num_entries: replace_info.num_entries as u32,
562 size: replace_info.size as u32,
563 entries,
564 num_counters: replace_info.num_counters,
565 valid_hooks: replace_info.valid_hooks.bits(),
566 hook_entry: replace_info.hook_entry,
567 underflow: replace_info.underflow,
568 counters: vec![],
569 };
570
571 let table_id = replace_info.table_id;
572 self.register_ebpf_programs(ip, table_id, ebpf_programs)?;
573
574 self.send_changes_to_net_filter(table.into_changes())?;
575
576 match ip {
577 Ip::V4 => self.ipv4[table_id] = iptable_entry,
578 Ip::V6 => self.ipv6[table_id] = iptable_entry,
579 }
580
581 Ok(())
582 }
583}
584
585pub struct IpTables {
587 state: OrderedRwLock<IpTablesState, KernelIpTables>,
588}
589
590impl IpTables {
591 pub fn new() -> Self {
592 Self {
596 state: OrderedRwLock::new(IpTablesState {
597 ipv4: default_ipv4_tables(),
598 ipv6: default_ipv6_tables(),
599 controller: LazyController::default(),
600 ebpf_programs: HashMap::new(),
601 }),
602 }
603 }
604
605 pub fn can_handle_getsockopt(level: u32, optname: u32) -> bool {
607 matches!(
608 (level, optname),
609 (
610 SOL_IP,
611 IPT_SO_GET_INFO
612 | IPT_SO_GET_ENTRIES
613 | IPT_SO_GET_REVISION_MATCH
614 | IPT_SO_GET_REVISION_TARGET,
615 ) | (
616 SOL_IPV6,
617 IP6T_SO_GET_INFO
618 | IP6T_SO_GET_ENTRIES
619 | IP6T_SO_GET_REVISION_MATCH
620 | IP6T_SO_GET_REVISION_TARGET,
621 )
622 )
623 }
624
625 pub fn can_handle_setsockopt(level: u32, optname: u32) -> bool {
627 matches!(
628 (level, optname),
629 (SOL_IP | SOL_IPV6, IPT_SO_SET_REPLACE | IPT_SO_SET_ADD_COUNTERS)
630 )
631 }
632
633 pub fn getsockopt(
634 &self,
635 locked: &mut Locked<Unlocked>,
636 current_task: &CurrentTask,
637 socket: &SocketHandle,
638 optname: u32,
639 mut optval: Vec<u8>,
640 ) -> Result<Vec<u8>, Errno> {
641 security::check_task_capable(current_task, CAP_NET_ADMIN)?;
642
643 if optval.is_empty() {
644 return error!(EINVAL);
645 }
646 if socket.socket_type != SocketType::Raw {
647 return error!(ENOPROTOOPT);
648 }
649
650 match optname {
651 IPT_SO_GET_INFO => {
653 if socket.domain == SocketDomain::Inet {
654 let (mut info, _) =
655 ipt_getinfo::read_from_prefix(&*optval).map_err(|_| errno!(EINVAL))?;
656 let Ok(table_id) = TableId::try_from(&info.name) else {
657 return error!(EINVAL);
658 };
659 let state = self.state.read(locked);
660 let table = &state.ipv4[table_id];
661 info.valid_hooks = table.valid_hooks;
662 info.hook_entry = table.hook_entry;
663 info.underflow = table.underflow;
664 info.num_entries = table.num_entries;
665 info.size = table.size;
666 Ok(info.as_bytes().to_vec())
667 } else {
668 let (mut info, _) =
669 ip6t_getinfo::read_from_prefix(&*optval).map_err(|_| errno!(EINVAL))?;
670 let Ok(table_id) = TableId::try_from(&info.name) else {
671 return error!(EINVAL);
672 };
673 let state = self.state.read(locked);
674 let table = &state.ipv6[table_id];
675 info.valid_hooks = table.valid_hooks;
676 info.hook_entry = table.hook_entry;
677 info.underflow = table.underflow;
678 info.num_entries = table.num_entries;
679 info.size = table.size;
680 Ok(info.as_bytes().to_vec())
681 }
682 }
683
684 IPT_SO_GET_ENTRIES => {
686 if socket.domain == SocketDomain::Inet {
687 let (get_entries, _) =
688 ipt_get_entries::read_from_prefix(&*optval).map_err(|_| errno!(EINVAL))?;
689 let Ok(table_id) = TableId::try_from(&get_entries.name) else {
690 return error!(EINVAL);
691 };
692 let mut entry_bytes = self.state.read(locked).ipv4[table_id].entries.clone();
693
694 if entry_bytes.len() > get_entries.size as usize {
695 log_warn!("Entries are longer than expected so truncating.");
696 entry_bytes.truncate(get_entries.size as usize);
697 }
698
699 optval.truncate(std::mem::size_of::<ipt_get_entries>());
700 optval.append(&mut entry_bytes);
701 } else {
702 let (get_entries, _) =
703 ip6t_get_entries::read_from_prefix(&*optval).map_err(|_| errno!(EINVAL))?;
704 let Ok(table_id) = TableId::try_from(&get_entries.name) else {
705 return error!(EINVAL);
706 };
707 let mut entry_bytes = self.state.read(locked).ipv6[table_id].entries.clone();
708
709 if entry_bytes.len() > get_entries.size as usize {
710 log_warn!("Entries are longer than expected so truncating.");
711 entry_bytes.truncate(get_entries.size as usize);
712 }
713
714 optval.truncate(std::mem::size_of::<ip6t_get_entries>());
715 optval.append(&mut entry_bytes);
716 }
717 Ok(optval)
718 }
719
720 IPT_SO_GET_REVISION_MATCH | IP6T_SO_GET_REVISION_MATCH => {
722 let (mut revision, _) =
723 xt_get_revision::read_from_prefix(&*optval).map_err(|_| errno!(EINVAL))?;
724 revision.revision = u8::MAX;
725 Ok(revision.as_bytes().to_vec())
726 }
727
728 IPT_SO_GET_REVISION_TARGET | IP6T_SO_GET_REVISION_TARGET => {
730 let (mut revision, _) =
731 xt_get_revision::read_from_prefix(&*optval).map_err(|_| errno!(EINVAL))?;
732 revision.revision = u8::MAX;
733 Ok(revision.as_bytes().to_vec())
734 }
735
736 _ => {
737 track_stub!(TODO("https://fxbug.dev/322875228"), "optname for network sockets");
738 Ok(vec![])
739 }
740 }
741 }
742
743 pub fn setsockopt(
744 &self,
745 locked: &mut Locked<Unlocked>,
746 current_task: &CurrentTask,
747 socket: &SocketHandle,
748 optname: u32,
749 optval: SockOptValue,
750 ) -> Result<(), Errno> {
751 security::check_task_capable(current_task, CAP_NET_ADMIN)?;
752
753 let mut bytes = optval.to_vec(current_task)?;
754 match optname {
755 IPT_SO_SET_REPLACE => {
757 if socket.domain == SocketDomain::Inet {
759 self.replace_ipv4_table(locked, current_task, bytes)
760 } else {
761 self.replace_ipv6_table(locked, current_task, bytes)
762 }
763 }
764
765 IPT_SO_SET_ADD_COUNTERS => {
767 let (counters_info, _) =
768 xt_counters_info::read_from_prefix(&*bytes).map_err(|_| errno!(EINVAL))?;
769
770 let Ok(table_id) = TableId::try_from(&counters_info.name) else {
771 return error!(EINVAL);
772 };
773
774 let mut state = self.state.write(locked);
775 let entry: &mut IpTable = match socket.domain {
776 SocketDomain::Inet => &mut state.ipv4[table_id],
777 _ => &mut state.ipv6[table_id],
778 };
779
780 entry.num_counters = counters_info.num_counters;
781 let mut counters = vec![];
782 bytes = bytes.split_off(std::mem::size_of::<xt_counters_info>());
783 for chunk in bytes.chunks(std::mem::size_of::<xt_counters>()) {
784 counters
785 .push(xt_counters::read_from_prefix(chunk).map_err(|_| errno!(EINVAL))?.0);
786 }
787 entry.counters = counters;
788 Ok(())
789 }
790
791 _ => Ok(()),
792 }
793 }
794
795 fn replace_ipv4_table(
796 &self,
797 locked: &mut Locked<Unlocked>,
798 current_task: &CurrentTask,
799 bytes: Vec<u8>,
800 ) -> Result<(), Errno> {
801 let mut ebpf_state = IpTablesEbpfState::new(locked, current_task);
802 let table =
803 iptables_utils::IpTable::from_ipt_replace(&mut ebpf_state, bytes).map_err(|e| {
804 log_warn!("Iptables: encountered error while parsing rules: {e}");
805 errno!(EINVAL)
806 })?;
807 let ebpf_programs = ebpf_state.take_programs();
808 self.state.write(locked).replace_table(Ip::V4, ebpf_programs, table)?;
809
810 Ok(())
811 }
812
813 fn replace_ipv6_table(
814 &self,
815 locked: &mut Locked<Unlocked>,
816 current_task: &CurrentTask,
817 bytes: Vec<u8>,
818 ) -> Result<(), Errno> {
819 let mut ebpf_state = IpTablesEbpfState::new(locked, current_task);
820 let table =
821 iptables_utils::IpTable::from_ip6t_replace(&mut ebpf_state, bytes).map_err(|e| {
822 log_warn!("Iptables: encountered error while parsing rules: {e}");
823 errno!(EINVAL)
824 })?;
825 let ebpf_programs = ebpf_state.take_programs();
826 self.state.write(locked).replace_table(Ip::V6, ebpf_programs, table)?;
827
828 Ok(())
829 }
830}
831
832struct IpTablesEbpfState<'a> {
833 locked: &'a mut Locked<Unlocked>,
834 current_task: &'a CurrentTask,
835 ebpf_programs: HashMap<febpf::ProgramId, ProgramHandle>,
836}
837
838impl<'a> IpTablesEbpfState<'a> {
839 fn new(locked: &'a mut Locked<Unlocked>, current_task: &'a CurrentTask) -> Self {
840 Self { locked, current_task, ebpf_programs: HashMap::default() }
841 }
842
843 fn take_programs(self) -> HashMap<febpf::ProgramId, ProgramHandle> {
844 self.ebpf_programs
845 }
846}
847
848impl<'a> IptReplaceContext for IpTablesEbpfState<'a> {
849 fn resolve_ebpf_socket_filter(
850 &mut self,
851 path: &BString,
852 ) -> Result<febpf::ProgramId, IpTableParseError> {
853 let program = resolve_pinned_bpf_object(
854 self.locked,
855 self.current_task,
856 path.as_ref(),
857 OpenFlags::RDONLY,
858 )
859 .and_then(|(handle, _node)| handle.into_program())
860 .map_err(|e| {
861 log_warn!("Failed to resolve eBPF program path {} for iptable matcher: {:?}", path, e);
862 IpTableParseError::InvalidEbpfProgramPath { path: path.clone() }
863 })?;
864
865 let id = program.fidl_id();
866 self.ebpf_programs.insert(id, program);
867 Ok(id)
868 }
869}