Skip to main content

starnix_core/task/
iptables.rs

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