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_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
49// The following arrays denote where built-in chains are defined for each table. This makes it easy
50// to calculate `hook_entry` and `underflow` for tables where built-in chains only have a policy and
51// no other rules by scaling by the size of a standard entry.
52//
53// The indices correspond to [PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING]. The first built-in
54// chain has value 0, second chain has value 1, and so on. Confusingly, built-in chains that do not
55// exist on a table are also denoted as 0, but this is how Linux expects these values.
56const 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/// Stores information about IP packet filter rules. Used to return information for
62/// IPT_SO_GET_INFO and IPT_SO_GET_ENTRIES.
63#[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/// Defines a set of table.
379#[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
400// `EbpfProgramState` keeps track of where an eBPF program is currently
401// installed. This ensures that we keep at least one reference to the program
402// while it is being used by a filter rule.
403struct 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 to configure net filtering state.
443    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::ErrorOnChange(_)),
495            ) => {
496                log_warn!(
497                    "IpTables: fuchsia.net.filter.NamespaceController/Commit \
498                    returned error: {e}"
499                );
500                error!(EINVAL)
501            }
502        }
503    }
504
505    /// Registers eBPF programs with the controller and updates the state for the specified table.
506    fn register_ebpf_programs(
507        &mut self,
508        ip: Ip,
509        table_id: TableId,
510        ebpf_programs: HashMap<febpf::ProgramId, ProgramHandle>,
511    ) -> Result<(), Errno> {
512        // Register new programs with the controller.
513        let controller = self.controller.get()?;
514        for (id, program_handle) in &ebpf_programs {
515            if !self.ebpf_programs.contains_key(id) {
516                let program: &Program = program_handle.deref();
517                match controller.register_ebpf_program(
518                    program.fidl_handle(),
519                    program.try_into()?,
520                    zx::MonotonicInstant::INFINITE,
521                ) {
522                    Ok(_) => {}
523                    Err(RegisterEbpfProgramError::AlreadyRegistered) => {}
524                    Err(e) => {
525                        log_warn!("IpTables: failed to register eBPF program: {e}");
526                        return error!(EINVAL);
527                    }
528                }
529            }
530        }
531
532        // Cleanup programs used in the previous version of the table.
533        self.ebpf_programs.retain(|_id, program_state| {
534            program_state.tables.remove(ip, table_id);
535
536            !program_state.tables.is_empty()
537        });
538
539        // Update the state for the new programs.
540        for (id, program_handle) in &ebpf_programs {
541            let entry = self.ebpf_programs.entry(*id).or_insert_with(|| EbpfProgramState {
542                program: program_handle.clone(),
543                tables: IpTableSet::default(),
544            });
545            entry.tables.add(ip, table_id);
546        }
547
548        Ok(())
549    }
550
551    fn replace_table(
552        &mut self,
553        ip: Ip,
554        ebpf_programs: HashMap<febpf::ProgramId, ProgramHandle>,
555        table: iptables_utils::IpTable,
556    ) -> Result<(), Errno> {
557        let entries = table.parser.entries_bytes().to_vec();
558        let replace_info = table.parser.replace_info.clone();
559        let iptable_entry = IpTable {
560            num_entries: replace_info.num_entries as u32,
561            size: replace_info.size as u32,
562            entries,
563            num_counters: replace_info.num_counters,
564            valid_hooks: replace_info.valid_hooks.bits(),
565            hook_entry: replace_info.hook_entry,
566            underflow: replace_info.underflow,
567            counters: vec![],
568        };
569
570        let table_id = replace_info.table_id;
571        self.register_ebpf_programs(ip, table_id, ebpf_programs)?;
572
573        self.send_changes_to_net_filter(table.into_changes())?;
574
575        match ip {
576            Ip::V4 => self.ipv4[table_id] = iptable_entry,
577            Ip::V6 => self.ipv6[table_id] = iptable_entry,
578        }
579
580        Ok(())
581    }
582}
583
584/// Stores [`IpTable`]s associated with each protocol.
585pub struct IpTables {
586    state: OrderedRwLock<IpTablesState, KernelIpTables>,
587}
588
589impl IpTables {
590    pub fn new() -> Self {
591        // Install default chains and policies on supported tables. These chains are expected to be
592        // present on the system before `iptables` client is ran.
593        // TODO(https://fxbug.dev/354766238): Propagated default rules to fuchsia.net.filter.
594        Self {
595            state: OrderedRwLock::new(IpTablesState {
596                ipv4: default_ipv4_tables(),
597                ipv6: default_ipv6_tables(),
598                controller: LazyController::default(),
599                ebpf_programs: HashMap::new(),
600            }),
601        }
602    }
603
604    /// Returns `true` if the sockopt can be handled by [`IpTables`].
605    pub fn can_handle_getsockopt(level: u32, optname: u32) -> bool {
606        matches!(
607            (level, optname),
608            (
609                SOL_IP,
610                IPT_SO_GET_INFO
611                    | IPT_SO_GET_ENTRIES
612                    | IPT_SO_GET_REVISION_MATCH
613                    | IPT_SO_GET_REVISION_TARGET,
614            ) | (
615                SOL_IPV6,
616                IP6T_SO_GET_INFO
617                    | IP6T_SO_GET_ENTRIES
618                    | IP6T_SO_GET_REVISION_MATCH
619                    | IP6T_SO_GET_REVISION_TARGET,
620            )
621        )
622    }
623
624    /// Returns `true` if the sockopt can be handled by [`IpTables`].
625    pub fn can_handle_setsockopt(level: u32, optname: u32) -> bool {
626        matches!(
627            (level, optname),
628            (SOL_IP | SOL_IPV6, IPT_SO_SET_REPLACE | IPT_SO_SET_ADD_COUNTERS)
629        )
630    }
631
632    pub fn getsockopt(
633        &self,
634        locked: &mut Locked<Unlocked>,
635        current_task: &CurrentTask,
636        socket: &SocketHandle,
637        optname: u32,
638        mut optval: Vec<u8>,
639    ) -> Result<Vec<u8>, Errno> {
640        security::check_task_capable(current_task, CAP_NET_ADMIN)?;
641
642        if optval.is_empty() {
643            return error!(EINVAL);
644        }
645        if socket.socket_type != SocketType::Raw {
646            return error!(ENOPROTOOPT);
647        }
648
649        match optname {
650            // Returns information about the table specified by `optval`.
651            IPT_SO_GET_INFO => {
652                if socket.domain == SocketDomain::Inet {
653                    let (mut info, _) =
654                        ipt_getinfo::read_from_prefix(&*optval).map_err(|_| errno!(EINVAL))?;
655                    let Ok(table_id) = TableId::try_from(&info.name) else {
656                        return error!(EINVAL);
657                    };
658                    let state = self.state.read(locked);
659                    let table = &state.ipv4[table_id];
660                    info.valid_hooks = table.valid_hooks;
661                    info.hook_entry = table.hook_entry;
662                    info.underflow = table.underflow;
663                    info.num_entries = table.num_entries;
664                    info.size = table.size;
665                    Ok(info.as_bytes().to_vec())
666                } else {
667                    let (mut info, _) =
668                        ip6t_getinfo::read_from_prefix(&*optval).map_err(|_| errno!(EINVAL))?;
669                    let Ok(table_id) = TableId::try_from(&info.name) else {
670                        return error!(EINVAL);
671                    };
672                    let state = self.state.read(locked);
673                    let table = &state.ipv6[table_id];
674                    info.valid_hooks = table.valid_hooks;
675                    info.hook_entry = table.hook_entry;
676                    info.underflow = table.underflow;
677                    info.num_entries = table.num_entries;
678                    info.size = table.size;
679                    Ok(info.as_bytes().to_vec())
680                }
681            }
682
683            // Returns the entries of the table specified by `optval`.
684            IPT_SO_GET_ENTRIES => {
685                if socket.domain == SocketDomain::Inet {
686                    let (get_entries, _) =
687                        ipt_get_entries::read_from_prefix(&*optval).map_err(|_| errno!(EINVAL))?;
688                    let Ok(table_id) = TableId::try_from(&get_entries.name) else {
689                        return error!(EINVAL);
690                    };
691                    let mut entry_bytes = self.state.read(locked).ipv4[table_id].entries.clone();
692
693                    if entry_bytes.len() > get_entries.size as usize {
694                        log_warn!("Entries are longer than expected so truncating.");
695                        entry_bytes.truncate(get_entries.size as usize);
696                    }
697
698                    optval.truncate(std::mem::size_of::<ipt_get_entries>());
699                    optval.append(&mut entry_bytes);
700                } else {
701                    let (get_entries, _) =
702                        ip6t_get_entries::read_from_prefix(&*optval).map_err(|_| errno!(EINVAL))?;
703                    let Ok(table_id) = TableId::try_from(&get_entries.name) else {
704                        return error!(EINVAL);
705                    };
706                    let mut entry_bytes = self.state.read(locked).ipv6[table_id].entries.clone();
707
708                    if entry_bytes.len() > get_entries.size as usize {
709                        log_warn!("Entries are longer than expected so truncating.");
710                        entry_bytes.truncate(get_entries.size as usize);
711                    }
712
713                    optval.truncate(std::mem::size_of::<ip6t_get_entries>());
714                    optval.append(&mut entry_bytes);
715                }
716                Ok(optval)
717            }
718
719            // Returns the revision match. Currently stubbed to return a max version number.
720            IPT_SO_GET_REVISION_MATCH | IP6T_SO_GET_REVISION_MATCH => {
721                let (mut revision, _) =
722                    xt_get_revision::read_from_prefix(&*optval).map_err(|_| errno!(EINVAL))?;
723                revision.revision = u8::MAX;
724                Ok(revision.as_bytes().to_vec())
725            }
726
727            // Returns the revision target. Currently stubbed to return a max version number.
728            IPT_SO_GET_REVISION_TARGET | IP6T_SO_GET_REVISION_TARGET => {
729                let (mut revision, _) =
730                    xt_get_revision::read_from_prefix(&*optval).map_err(|_| errno!(EINVAL))?;
731                revision.revision = u8::MAX;
732                Ok(revision.as_bytes().to_vec())
733            }
734
735            _ => {
736                track_stub!(TODO("https://fxbug.dev/322875228"), "optname for network sockets");
737                Ok(vec![])
738            }
739        }
740    }
741
742    pub fn setsockopt(
743        &self,
744        locked: &mut Locked<Unlocked>,
745        current_task: &CurrentTask,
746        socket: &SocketHandle,
747        optname: u32,
748        optval: SockOptValue,
749    ) -> Result<(), Errno> {
750        security::check_task_capable(current_task, CAP_NET_ADMIN)?;
751
752        let mut bytes = optval.to_vec(current_task)?;
753        match optname {
754            // Replaces the [`IpTable`] specified by `user_opt`.
755            IPT_SO_SET_REPLACE => {
756                // TODO(https://fxbug.dev/407842082): The following logic needs to be fixed.
757                if socket.domain == SocketDomain::Inet {
758                    self.replace_ipv4_table(locked, current_task, bytes)
759                } else {
760                    self.replace_ipv6_table(locked, current_task, bytes)
761                }
762            }
763
764            // Sets the counters of the [`IpTable`] specified by `user_opt`.
765            IPT_SO_SET_ADD_COUNTERS => {
766                let (counters_info, _) =
767                    xt_counters_info::read_from_prefix(&*bytes).map_err(|_| errno!(EINVAL))?;
768
769                let Ok(table_id) = TableId::try_from(&counters_info.name) else {
770                    return error!(EINVAL);
771                };
772
773                let mut state = self.state.write(locked);
774                let entry: &mut IpTable = match socket.domain {
775                    SocketDomain::Inet => &mut state.ipv4[table_id],
776                    _ => &mut state.ipv6[table_id],
777                };
778
779                entry.num_counters = counters_info.num_counters;
780                let mut counters = vec![];
781                bytes = bytes.split_off(std::mem::size_of::<xt_counters_info>());
782                for chunk in bytes.chunks(std::mem::size_of::<xt_counters>()) {
783                    counters
784                        .push(xt_counters::read_from_prefix(chunk).map_err(|_| errno!(EINVAL))?.0);
785                }
786                entry.counters = counters;
787                Ok(())
788            }
789
790            _ => Ok(()),
791        }
792    }
793
794    fn replace_ipv4_table(
795        &self,
796        locked: &mut Locked<Unlocked>,
797        current_task: &CurrentTask,
798        bytes: Vec<u8>,
799    ) -> Result<(), Errno> {
800        let mut ebpf_state = IpTablesEbpfState::new(locked, current_task);
801        let table =
802            iptables_utils::IpTable::from_ipt_replace(&mut ebpf_state, bytes).map_err(|e| {
803                log_warn!("Iptables: encountered error while parsing rules: {e}");
804                errno!(EINVAL)
805            })?;
806        let ebpf_programs = ebpf_state.take_programs();
807        self.state.write(locked).replace_table(Ip::V4, ebpf_programs, table)?;
808
809        Ok(())
810    }
811
812    fn replace_ipv6_table(
813        &self,
814        locked: &mut Locked<Unlocked>,
815        current_task: &CurrentTask,
816        bytes: Vec<u8>,
817    ) -> Result<(), Errno> {
818        let mut ebpf_state = IpTablesEbpfState::new(locked, current_task);
819        let table =
820            iptables_utils::IpTable::from_ip6t_replace(&mut ebpf_state, bytes).map_err(|e| {
821                log_warn!("Iptables: encountered error while parsing rules: {e}");
822                errno!(EINVAL)
823            })?;
824        let ebpf_programs = ebpf_state.take_programs();
825        self.state.write(locked).replace_table(Ip::V6, ebpf_programs, table)?;
826
827        Ok(())
828    }
829}
830
831struct IpTablesEbpfState<'a> {
832    locked: &'a mut Locked<Unlocked>,
833    current_task: &'a CurrentTask,
834    ebpf_programs: HashMap<febpf::ProgramId, ProgramHandle>,
835}
836
837impl<'a> IpTablesEbpfState<'a> {
838    fn new(locked: &'a mut Locked<Unlocked>, current_task: &'a CurrentTask) -> Self {
839        Self { locked, current_task, ebpf_programs: HashMap::default() }
840    }
841
842    fn take_programs(self) -> HashMap<febpf::ProgramId, ProgramHandle> {
843        self.ebpf_programs
844    }
845}
846
847impl<'a> IptReplaceContext for IpTablesEbpfState<'a> {
848    fn resolve_ebpf_socket_filter(
849        &mut self,
850        path: &BString,
851    ) -> Result<febpf::ProgramId, IpTableParseError> {
852        let program = resolve_pinned_bpf_object(
853            self.locked,
854            self.current_task,
855            path.as_ref(),
856            OpenFlags::RDONLY,
857        )
858        .and_then(|(handle, _node)| handle.into_program())
859        .map_err(|e| {
860            log_warn!("Failed to resolve eBPF program path {} for iptable matcher: {:?}", path, e);
861            IpTableParseError::InvalidEbpfProgramPath { path: path.clone() }
862        })?;
863
864        let id = program.fidl_id();
865        self.ebpf_programs.insert(id, program);
866        Ok(id)
867    }
868}