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_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::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    /// Registers eBPF programs with the controller and updates the state for the specified table.
507    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        // Register new programs with the controller.
514        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        // Cleanup programs used in the previous version of the table.
534        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        // Update the state for the new programs.
541        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
585/// Stores [`IpTable`]s associated with each protocol.
586pub struct IpTables {
587    state: OrderedRwLock<IpTablesState, KernelIpTables>,
588}
589
590impl IpTables {
591    pub fn new() -> Self {
592        // Install default chains and policies on supported tables. These chains are expected to be
593        // present on the system before `iptables` client is ran.
594        // TODO(https://fxbug.dev/354766238): Propagated default rules to fuchsia.net.filter.
595        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    /// Returns `true` if the sockopt can be handled by [`IpTables`].
606    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    /// Returns `true` if the sockopt can be handled by [`IpTables`].
626    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            // Returns information about the table specified by `optval`.
652            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            // Returns the entries of the table specified by `optval`.
685            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            // Returns the revision match. Currently stubbed to return a max version number.
721            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            // Returns the revision target. Currently stubbed to return a max version number.
729            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            // Replaces the [`IpTable`] specified by `user_opt`.
756            IPT_SO_SET_REPLACE => {
757                // TODO(https://fxbug.dev/407842082): The following logic needs to be fixed.
758                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            // Sets the counters of the [`IpTable`] specified by `user_opt`.
766            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}