Skip to main content

starnix_modules_procfs/
sys_net.rs

1// Copyright 2025 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 std::sync::atomic::Ordering;
6
7use crate::sysctl_directory::SysctlDirectory;
8use fidl::endpoints::DiscoverableProtocolMarker as _;
9use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
10use fidl_fuchsia_net_root as fnet_root;
11use fidl_fuchsia_net_settings as fnet_settings;
12use fuchsia_component::client::connect_to_protocol_sync;
13use net_types::ip::{Ip, IpVersion, Ipv4, Ipv6};
14use netlink::{SysctlError, SysctlInterfaceSelector};
15use starnix_core::task::CurrentTask;
16use starnix_core::vfs::pseudo::simple_file::{
17    BytesFile, BytesFileOps, SimpleFileNode, parse_i32_file, serialize_for_file,
18};
19use starnix_core::vfs::pseudo::stub_bytes_file::StubBytesFile;
20use starnix_core::vfs::{
21    DirEntry, DirectoryEntryType, DirentSink, FileObject, FileOps, FsNode, FsNodeHandle, FsNodeOps,
22    FsStr, emit_dotdot, fileops_impl_directory, fileops_impl_noop_sync,
23    fileops_impl_unbounded_seek, fs_node_impl_dir_readonly,
24};
25use starnix_logging::{bug_ref, log_error, log_warn, track_stub};
26
27use starnix_uapi::errors::Errno;
28use starnix_uapi::file_mode::{FileMode, mode};
29use starnix_uapi::open_flags::OpenFlags;
30use starnix_uapi::vfs::FdEvents;
31use starnix_uapi::{errno, error, uapi};
32use std::borrow::Cow;
33
34const FILE_MODE: FileMode = mode!(IFREG, 0o644);
35
36fn netstack_devices_readdir(
37    file: &FileObject,
38    current_task: &CurrentTask,
39    sink: &mut dyn DirentSink,
40) -> Result<(), Errno> {
41    file.blocking_op(current_task, FdEvents::empty(), None, || {
42        let (initialized, _) = &current_task.kernel().netstack_devices.initialized_and_wq;
43        if !initialized.load(Ordering::SeqCst) {
44            // Kick off the initialization of the netlink worker if not yet.
45            let _ = current_task.kernel().network_netlink();
46            return error!(EAGAIN);
47        }
48        emit_dotdot(file, sink)?;
49
50        if sink.offset() == 2 {
51            sink.add(
52                file.fs.allocate_ino(),
53                sink.offset() + 1,
54                DirectoryEntryType::from_mode(FILE_MODE),
55                "all".into(),
56            )?;
57        }
58
59        if sink.offset() == 3 {
60            sink.add(
61                file.fs.allocate_ino(),
62                sink.offset() + 1,
63                DirectoryEntryType::from_mode(FILE_MODE),
64                "default".into(),
65            )?;
66        }
67
68        let devices = current_task.kernel().netstack_devices.snapshot_devices();
69        for (name, _) in devices.iter().skip(sink.offset() as usize - 4) {
70            let inode_num = file.fs.allocate_ino();
71            sink.add(
72                inode_num,
73                sink.offset() + 1,
74                DirectoryEntryType::from_mode(FILE_MODE),
75                name.as_ref(),
76            )?;
77        }
78        Ok(())
79    })
80}
81
82macro_rules! fileops_impl_netstack_devices {
83    () => {
84        fn readdir(
85            &self,
86            file: &FileObject,
87            current_task: &CurrentTask,
88            sink: &mut dyn DirentSink,
89        ) -> Result<(), Errno> {
90            netstack_devices_readdir(file, current_task, sink)
91        }
92
93        fn wait_async(
94            &self,
95            _file: &FileObject,
96            current_task: &CurrentTask,
97            waiter: &starnix_core::task::Waiter,
98            _events: FdEvents,
99            _handler: starnix_core::task::EventHandler,
100        ) -> Option<starnix_core::task::WaitCanceler> {
101            let (_initialized, wq) = &current_task.kernel().netstack_devices.initialized_and_wq;
102            Some(wq.wait_async(waiter))
103        }
104    };
105}
106
107fn get_netstack_device(
108    current_task: &CurrentTask,
109    name: &FsStr,
110) -> Option<SysctlInterfaceSelector> {
111    // Kick off the initialization of netlink worker.
112    let _ = current_task.kernel().network_netlink();
113    // Per https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt,
114    //
115    //   conf/default/*:
116    //	   Change the interface-specific default settings.
117    //
118    //   conf/all/*:
119    //	   Change all the interface-specific settings.
120    //
121    // Note that the all/default directories don't exist in `/sys/class/net`.
122    if name == "all" {
123        return Some(SysctlInterfaceSelector::All);
124    }
125    if name == "default" {
126        return Some(SysctlInterfaceSelector::Default);
127    }
128    if let Some(dev) = current_task.kernel().netstack_devices.get_device(name) {
129        return Some(SysctlInterfaceSelector::Id(dev.interface_id));
130    }
131    None
132}
133
134#[derive(Clone)]
135pub struct ProcSysNetIpv4Conf;
136
137impl FsNodeOps for ProcSysNetIpv4Conf {
138    fs_node_impl_dir_readonly!();
139
140    fn create_file_ops(
141        &self,
142        _node: &FsNode,
143        _current_task: &CurrentTask,
144        _flags: OpenFlags,
145    ) -> Result<Box<dyn FileOps>, Errno> {
146        Ok(Box::new(self.clone()))
147    }
148
149    fn lookup(
150        &self,
151        entry: &DirEntry,
152        current_task: &CurrentTask,
153        name: &FsStr,
154    ) -> Result<FsNodeHandle, Errno> {
155        if get_netstack_device(current_task, name).is_some() {
156            let fs = entry.node.fs();
157            let dir = SysctlDirectory::<{ uapi::CAP_NET_ADMIN }>::new();
158            dir.edit(&fs, |dir| {
159                dir.entry(
160                    "accept_redirects",
161                    StubBytesFile::new_node(bug_ref!("https://fxbug.dev/423646442")),
162                    FILE_MODE,
163                );
164            });
165            return Ok(dir.into_node(&fs, 0o555));
166        }
167        error!(ENOENT, "looking for {name}")
168    }
169}
170
171impl FileOps for ProcSysNetIpv4Conf {
172    fileops_impl_directory!();
173    fileops_impl_noop_sync!();
174    fileops_impl_unbounded_seek!();
175    fileops_impl_netstack_devices!();
176}
177
178#[derive(Clone)]
179pub struct ProcSysNetIpv4Neigh;
180
181impl FsNodeOps for ProcSysNetIpv4Neigh {
182    fs_node_impl_dir_readonly!();
183
184    fn create_file_ops(
185        &self,
186        _node: &FsNode,
187        _current_task: &CurrentTask,
188        _flags: OpenFlags,
189    ) -> Result<Box<dyn FileOps>, Errno> {
190        Ok(Box::new(self.clone()))
191    }
192
193    fn lookup(
194        &self,
195        entry: &DirEntry,
196        current_task: &CurrentTask,
197        name: &FsStr,
198    ) -> Result<FsNodeHandle, Errno> {
199        if let Some(interface) = get_netstack_device(current_task, name) {
200            let fs = entry.node.fs();
201            let dir = SysctlDirectory::<{ uapi::CAP_NET_ADMIN }>::new();
202            dir.edit(&fs, |dir| {
203                dir.entry(
204                    "ucast_solicit",
205                    new_interface_config_file_node::<UcastSolicit<Ipv4>>(interface),
206                    FILE_MODE,
207                );
208                dir.entry(
209                    "retrans_time_ms",
210                    new_interface_config_file_node::<RetransTimeMs<Ipv4>>(interface),
211                    FILE_MODE,
212                );
213                dir.entry(
214                    "mcast_resolicit",
215                    new_interface_config_file_node::<McastResolicit<Ipv4>>(interface),
216                    FILE_MODE,
217                );
218                dir.entry(
219                    "base_reachable_time_ms",
220                    new_interface_config_file_node::<BaseReachableTimeMs<Ipv4>>(interface),
221                    FILE_MODE,
222                );
223            });
224            return Ok(dir.into_node(&fs, 0o555));
225        }
226        error!(ENOENT, "looking for {name}")
227    }
228}
229
230impl FileOps for ProcSysNetIpv4Neigh {
231    fileops_impl_directory!();
232    fileops_impl_noop_sync!();
233    fileops_impl_unbounded_seek!();
234    fileops_impl_netstack_devices!();
235}
236
237#[derive(Clone)]
238pub struct ProcSysNetIpv6Conf;
239
240impl FsNodeOps for ProcSysNetIpv6Conf {
241    fs_node_impl_dir_readonly!();
242
243    fn create_file_ops(
244        &self,
245        _node: &FsNode,
246        _current_task: &CurrentTask,
247        _flags: OpenFlags,
248    ) -> Result<Box<dyn FileOps>, Errno> {
249        Ok(Box::new(self.clone()))
250    }
251
252    fn lookup(
253        &self,
254        entry: &DirEntry,
255        current_task: &CurrentTask,
256        name: &FsStr,
257    ) -> Result<FsNodeHandle, Errno> {
258        if let Some(interface) = get_netstack_device(current_task, name) {
259            let fs = entry.node.fs();
260            let dir = SysctlDirectory::<{ uapi::CAP_NET_ADMIN }>::new();
261            dir.edit(&fs, |dir| {
262                dir.entry(
263                    "accept_ra",
264                    StubBytesFile::new_node(bug_ref!("https://fxbug.dev/423646365")),
265                    FILE_MODE,
266                );
267                dir.entry(
268                    "accept_ra_defrtr",
269                    new_interface_config_file_node::<AcceptRaDefrtr>(interface),
270                    FILE_MODE,
271                );
272                dir.entry(
273                    "accept_ra_info_min_plen",
274                    StubBytesFile::new_node(bug_ref!("https://fxbug.dev/423645816")),
275                    FILE_MODE,
276                );
277                dir.entry(
278                    "accept_ra_rt_info_min_plen",
279                    StubBytesFile::new_node(bug_ref!("https://fxbug.dev/322908046")),
280                    FILE_MODE,
281                );
282                dir.entry(
283                    "accept_ra_rt_table",
284                    NetworkNetlinkSysctlFile::new_node(interface),
285                    FILE_MODE,
286                );
287                dir.entry(
288                    "accept_redirects",
289                    StubBytesFile::new_node(bug_ref!("https://fxbug.dev/423646442")),
290                    FILE_MODE,
291                );
292                dir.entry(
293                    "dad_transmits",
294                    new_interface_config_file_node::<Ipv6DadTransmits>(interface),
295                    FILE_MODE,
296                );
297                dir.entry(
298                    "use_tempaddr",
299                    new_interface_config_file_node::<UseTempAddr>(interface),
300                    FILE_MODE,
301                );
302                dir.entry(
303                    "addr_gen_mode",
304                    StubBytesFile::new_node(bug_ref!("https://fxbug.dev/423645864")),
305                    FILE_MODE,
306                );
307                dir.entry(
308                    "stable_secret",
309                    StubBytesFile::new_node(bug_ref!("https://fxbug.dev/423646722")),
310                    FILE_MODE,
311                );
312                dir.entry(
313                    "disable_ipv6",
314                    new_interface_config_file_node::<DisableIpv6>(interface),
315                    FILE_MODE,
316                );
317                dir.entry(
318                    "optimistic_dad",
319                    StubBytesFile::new_node(bug_ref!("https://fxbug.dev/423646584")),
320                    FILE_MODE,
321                );
322                dir.entry(
323                    "use_oif_addrs_only",
324                    StubBytesFile::new_node(bug_ref!("https://fxbug.dev/423645421")),
325                    FILE_MODE,
326                );
327                dir.entry(
328                    "use_optimistic",
329                    StubBytesFile::new_node(bug_ref!("https://fxbug.dev/423645883")),
330                    FILE_MODE,
331                );
332                dir.entry(
333                    "forwarding",
334                    StubBytesFile::new_node(bug_ref!("https://fxbug.dev/322907925")),
335                    FILE_MODE,
336                );
337            });
338            return Ok(dir.into_node(&fs, 0o555));
339        }
340        error!(ENOENT, "looking for {name}")
341    }
342}
343
344impl FileOps for ProcSysNetIpv6Conf {
345    fileops_impl_directory!();
346    fileops_impl_noop_sync!();
347    fileops_impl_unbounded_seek!();
348    fileops_impl_netstack_devices!();
349}
350
351#[derive(Clone)]
352pub struct ProcSysNetIpv6Neigh;
353
354impl FsNodeOps for ProcSysNetIpv6Neigh {
355    fs_node_impl_dir_readonly!();
356
357    fn create_file_ops(
358        &self,
359        _node: &FsNode,
360        _current_task: &CurrentTask,
361        _flags: OpenFlags,
362    ) -> Result<Box<dyn FileOps>, Errno> {
363        Ok(Box::new(self.clone()))
364    }
365
366    fn lookup(
367        &self,
368        entry: &DirEntry,
369        current_task: &CurrentTask,
370        name: &FsStr,
371    ) -> Result<FsNodeHandle, Errno> {
372        if let Some(interface) = get_netstack_device(current_task, name) {
373            let fs = entry.node.fs();
374            let dir = SysctlDirectory::<{ uapi::CAP_NET_ADMIN }>::new();
375            dir.edit(&fs, |dir| {
376                dir.entry(
377                    "ucast_solicit",
378                    new_interface_config_file_node::<UcastSolicit<Ipv6>>(interface),
379                    FILE_MODE,
380                );
381                dir.entry(
382                    "retrans_time_ms",
383                    new_interface_config_file_node::<RetransTimeMs<Ipv6>>(interface),
384                    FILE_MODE,
385                );
386                dir.entry(
387                    "mcast_resolicit",
388                    new_interface_config_file_node::<McastResolicit<Ipv6>>(interface),
389                    FILE_MODE,
390                );
391                dir.entry(
392                    "base_reachable_time_ms",
393                    new_interface_config_file_node::<BaseReachableTimeMs<Ipv6>>(interface),
394                    FILE_MODE,
395                );
396            });
397            return Ok(dir.into_node(&fs, 0o555));
398        }
399        error!(ENOENT, "looking for {name}")
400    }
401}
402
403impl FileOps for ProcSysNetIpv6Neigh {
404    fileops_impl_directory!();
405    fileops_impl_noop_sync!();
406    fileops_impl_unbounded_seek!();
407    fileops_impl_netstack_devices!();
408}
409
410struct NetworkNetlinkSysctlFile {
411    interface: SysctlInterfaceSelector,
412}
413
414impl NetworkNetlinkSysctlFile {
415    fn new_node(interface: SysctlInterfaceSelector) -> impl FsNodeOps {
416        SimpleFileNode::new(move |_| Ok(BytesFile::new(Self { interface })))
417    }
418}
419
420fn to_errno(error: SysctlError) -> Errno {
421    match error {
422        SysctlError::Disconnected => errno!(EIO),
423        SysctlError::NoInterface => errno!(ENODEV),
424        SysctlError::Unsupported => errno!(ENOTSUP),
425    }
426}
427
428impl BytesFileOps for NetworkNetlinkSysctlFile {
429    fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
430        let value = parse_i32_file(&data)?;
431        current_task
432            .kernel()
433            .network_netlink()
434            .write_accept_ra_rt_table(self.interface, value)
435            .map_err(|err| {
436                log_error!("failed to write to {:?}: {:?}", self.interface, err);
437                to_errno(err)
438            })
439    }
440
441    fn read(&self, current_task: &CurrentTask) -> Result<std::borrow::Cow<'_, [u8]>, Errno> {
442        let value = current_task
443            .kernel()
444            .network_netlink()
445            .read_accept_ra_rt_table(self.interface)
446            .map_err(|err| {
447                log_error!("failed to read from {:?}: {:?}", self.interface, err);
448                to_errno(err)
449            })?;
450        Ok(serialize_for_file(value).into())
451    }
452}
453
454pub struct PingGroupRangeFile;
455
456impl PingGroupRangeFile {
457    const MAX_GID: u32 = 4294967294;
458
459    pub fn new_node() -> impl FsNodeOps {
460        BytesFile::new_node(Self)
461    }
462}
463
464impl BytesFileOps for PingGroupRangeFile {
465    fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
466        let mut params = std::str::from_utf8(&data)
467            .map_err(|_| errno!(EINVAL))?
468            .trim_ascii()
469            .split_ascii_whitespace();
470        let min = params
471            .next()
472            .ok_or_else(|| errno!(EINVAL))?
473            .parse::<u32>()
474            .map_err(|_| errno!(EINVAL))?;
475        if min > Self::MAX_GID {
476            return error!(EINVAL);
477        }
478
479        // Max value is optional.
480        let max = match params.next() {
481            Some(v) => {
482                let v = v.parse::<u32>().map_err(|_| errno!(EINVAL))?;
483                if v > Self::MAX_GID {
484                    return error!(EINVAL);
485                }
486                Some(v + 1)
487            }
488            None => None,
489        };
490
491        let mut range = current_task.kernel().system_limits.socket.icmp_ping_gids.lock();
492        range.start = min;
493        if let Some(max) = max {
494            range.end = max;
495        }
496        if range.is_empty() {
497            // Default to "[1, 0]" range (equivalent to "[1, 1)") to match
498            // Linux behavior.
499            *range = 1..1;
500        }
501
502        Ok(())
503    }
504    fn read(&self, current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
505        let range = current_task.kernel().system_limits.socket.icmp_ping_gids.lock().clone();
506        Ok(format!("{}\t{}\n", range.start, range.end - 1).into_bytes().into())
507    }
508}
509
510trait InterfaceConfig: Sync + Send + 'static {
511    fn try_from_i32(value: i32) -> Result<fidl_fuchsia_net_interfaces_admin::Configuration, Errno>;
512    fn try_into_i32(config: fidl_fuchsia_net_interfaces_admin::Configuration)
513    -> Result<i32, Errno>;
514}
515
516fn new_interface_config_file_node<Config>(selector: SysctlInterfaceSelector) -> impl FsNodeOps
517where
518    Config: InterfaceConfig,
519{
520    SimpleFileNode::new(move |_| {
521        Ok(BytesFile::new(InterfaceConfigFile {
522            selector,
523            _marker: std::marker::PhantomData::<Config>,
524        }))
525    })
526}
527
528struct InterfaceConfigFile<Config> {
529    selector: SysctlInterfaceSelector,
530    _marker: std::marker::PhantomData<Config>,
531}
532
533impl<Config> BytesFileOps for InterfaceConfigFile<Config>
534where
535    Config: InterfaceConfig,
536{
537    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
538        let config = Config::try_from_i32(parse_i32_file(&data)?)?;
539        set_interface_config(self.selector, &config)?;
540        Ok(())
541    }
542    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
543        let config = Config::try_into_i32(get_interface_config(self.selector)?)?;
544        Ok(serialize_for_file::<i32>(config).into())
545    }
546}
547
548fn set_interface_config(
549    selector: SysctlInterfaceSelector,
550    config: &fidl_fuchsia_net_interfaces_admin::Configuration,
551) -> Result<(), Errno> {
552    match selector {
553        SysctlInterfaceSelector::All => {
554            log_warn!("setting config for all network interfaces is ignored");
555            Ok(())
556        }
557        SysctlInterfaceSelector::Default => {
558            let control =
559                connect_to_protocol_sync::<fnet_settings::ControlMarker>().map_err(|err| {
560                    log_error!(
561                        "failed to connect to {}: {:?}",
562                        fnet_settings::ControlMarker::PROTOCOL_NAME,
563                        err
564                    );
565                    errno!(EIO)
566                })?;
567            control
568                .update_interface_defaults(config, zx::MonotonicInstant::INFINITE)
569                .map_err(|err| {
570                    log_error!("failed to set network interface config: {:?}", err);
571                    errno!(EIO)
572                })?
573                .map_err(map_update_error)?;
574            Ok(())
575        }
576        SysctlInterfaceSelector::Id(id) => {
577            let root =
578                connect_to_protocol_sync::<fnet_root::InterfacesMarker>().map_err(|err| {
579                    log_error!(
580                        "failed to connect to {}: {:?}",
581                        fnet_root::InterfacesMarker::PROTOCOL_NAME,
582                        err
583                    );
584                    errno!(EIO)
585                })?;
586            let (control, server) = fidl::endpoints::create_sync_proxy();
587            root.get_admin(id.get(), server).map_err(|err| {
588                log_error!("failed to get network interface: {:?}", err);
589                errno!(EIO)
590            })?;
591            let _prev = control
592                .set_configuration(config, zx::MonotonicInstant::INFINITE)
593                .map_err(|err| {
594                    if err.is_closed() {
595                        log_error!("network interface {} went away", id);
596                        errno!(ENODEV)
597                    } else {
598                        log_error!("failed to set network interface config: {:?}", err);
599                        errno!(EIO)
600                    }
601                })?
602                .map_err(|err| {
603                    use fnet_interfaces_admin::ControlSetConfigurationError;
604                    match err {
605                        ControlSetConfigurationError::Ipv4ForwardingUnsupported
606                        | ControlSetConfigurationError::Ipv4MulticastForwardingUnsupported
607                        | ControlSetConfigurationError::Ipv4IgmpVersionUnsupported
608                        | ControlSetConfigurationError::Ipv6ForwardingUnsupported
609                        | ControlSetConfigurationError::Ipv6MulticastForwardingUnsupported
610                        | ControlSetConfigurationError::Ipv6MldVersionUnsupported
611                        | ControlSetConfigurationError::ArpNotSupported
612                        | ControlSetConfigurationError::NdpNotSupported => errno!(ENOTSUP),
613                        ControlSetConfigurationError::IllegalZeroValue
614                        | ControlSetConfigurationError::IllegalNegativeValue => errno!(EINVAL),
615                        ControlSetConfigurationError::__SourceBreaking { unknown_ordinal } => {
616                            log_error!("unknown error with ordinal: {unknown_ordinal}");
617                            errno!(EIO)
618                        }
619                    }
620                });
621            Ok(())
622        }
623    }
624}
625
626fn get_interface_defaults() -> Result<fidl_fuchsia_net_interfaces_admin::Configuration, Errno> {
627    let state = connect_to_protocol_sync::<fnet_settings::StateMarker>().map_err(|err| {
628        log_error!("failed to connect to {}: {:?}", fnet_settings::StateMarker::PROTOCOL_NAME, err);
629        errno!(EIO)
630    })?;
631    let config = state.get_interface_defaults(zx::MonotonicInstant::INFINITE).map_err(|err| {
632        log_error!("failed to get network interface defaults: {:?}", err);
633        if err.is_closed() { errno!(ENODEV) } else { errno!(EIO) }
634    })?;
635    Ok(config)
636}
637
638fn get_interface_config(
639    selector: SysctlInterfaceSelector,
640) -> Result<fidl_fuchsia_net_interfaces_admin::Configuration, Errno> {
641    match selector {
642        SysctlInterfaceSelector::All => {
643            track_stub!(
644                TODO("https://fxbug.dev/521344735"),
645                "procfs sys net 'all' interface config"
646            );
647            get_interface_defaults()
648        }
649        SysctlInterfaceSelector::Default => get_interface_defaults(),
650        SysctlInterfaceSelector::Id(id) => {
651            let root =
652                connect_to_protocol_sync::<fnet_root::InterfacesMarker>().map_err(|err| {
653                    log_error!(
654                        "failed to connect to {}: {:?}",
655                        fnet_root::InterfacesMarker::PROTOCOL_NAME,
656                        err
657                    );
658                    errno!(EIO)
659                })?;
660            let (control, server) = fidl::endpoints::create_sync_proxy();
661            root.get_admin(id.get(), server).map_err(|err| {
662                log_error!("failed to get network interface: {:?}", err);
663                errno!(EIO)
664            })?;
665            let config = control
666                .get_configuration(zx::MonotonicInstant::INFINITE)
667                .map_err(|err| {
668                    log_error!("failed to get network interface config: {:?}", err);
669                    if err.is_closed() { errno!(ENODEV) } else { errno!(EIO) }
670                })?
671                .map_err(|err| match err {
672                    fnet_interfaces_admin::ControlGetConfigurationError::__SourceBreaking {
673                        unknown_ordinal,
674                    } => {
675                        log_error!("unknown error with ordinal: {unknown_ordinal}");
676                        errno!(EIO)
677                    }
678                })?;
679            Ok(config)
680        }
681    }
682}
683
684struct DisableIpv6;
685
686impl InterfaceConfig for DisableIpv6 {
687    fn try_from_i32(value: i32) -> Result<fidl_fuchsia_net_interfaces_admin::Configuration, Errno> {
688        Ok(fidl_fuchsia_net_interfaces_admin::Configuration {
689            ipv6: Some(fidl_fuchsia_net_interfaces_admin::Ipv6Configuration {
690                enabled: Some(value == 0),
691                ..Default::default()
692            }),
693            ..Default::default()
694        })
695    }
696    fn try_into_i32(
697        config: fidl_fuchsia_net_interfaces_admin::Configuration,
698    ) -> Result<i32, Errno> {
699        let ipv6 = config.ipv6.ok_or_else(|| {
700            log_error!("network interface config missing ipv6");
701            errno!(EIO)
702        })?;
703        let enabled = ipv6.enabled.ok_or_else(|| {
704            log_error!("network interface config missing ipv6 enabled");
705            errno!(EIO)
706        })?;
707        Ok(i32::from(!enabled))
708    }
709}
710
711struct UcastSolicit<I: Ip> {
712    _marker: core::marker::PhantomData<I>,
713}
714
715impl<I: Ip> InterfaceConfig for UcastSolicit<I> {
716    fn try_from_i32(value: i32) -> Result<fnet_interfaces_admin::Configuration, Errno> {
717        let max_unicast_solicitations = u16::try_from(value).map_err(|_| errno!(EINVAL))?;
718        let nud_config = fnet_interfaces_admin::NudConfiguration {
719            max_unicast_solicitations: Some(max_unicast_solicitations),
720            ..Default::default()
721        };
722        let mut config = fnet_interfaces_admin::Configuration::default();
723        match I::VERSION {
724            IpVersion::V4 => {
725                config.ipv4 = Some(fidl_fuchsia_net_interfaces_admin::Ipv4Configuration {
726                    arp: Some(fnet_interfaces_admin::ArpConfiguration {
727                        nud: Some(nud_config),
728                        ..Default::default()
729                    }),
730                    ..Default::default()
731                })
732            }
733            IpVersion::V6 => {
734                config.ipv6 = Some(fidl_fuchsia_net_interfaces_admin::Ipv6Configuration {
735                    ndp: Some(fnet_interfaces_admin::NdpConfiguration {
736                        nud: Some(nud_config),
737                        ..Default::default()
738                    }),
739                    ..Default::default()
740                })
741            }
742        }
743        Ok(config)
744    }
745
746    fn try_into_i32(config: fnet_interfaces_admin::Configuration) -> Result<i32, Errno> {
747        let max_unicast_solicitations = match I::VERSION {
748            IpVersion::V4 => config
749                .ipv4
750                .and_then(|ipv4| ipv4.arp)
751                .and_then(|arp| arp.nud)
752                .and_then(|nud| nud.max_unicast_solicitations)
753                .ok_or_else(|| {
754                    log_error!(
755                        "network interface config missing ipv4 arp max_unicast_solicitations"
756                    );
757                    errno!(EIO)
758                })?,
759            IpVersion::V6 => config
760                .ipv6
761                .and_then(|ipv6| ipv6.ndp)
762                .and_then(|ndp| ndp.nud)
763                .and_then(|nud| nud.max_unicast_solicitations)
764                .ok_or_else(|| {
765                    log_error!(
766                        "network interface config missing ipv6 ndp max_unicast_solicitations"
767                    );
768                    errno!(EIO)
769                })?,
770        };
771        Ok(i32::from(max_unicast_solicitations))
772    }
773}
774
775struct McastResolicit<I: Ip> {
776    _marker: core::marker::PhantomData<I>,
777}
778
779impl<I: Ip> InterfaceConfig for McastResolicit<I> {
780    fn try_from_i32(value: i32) -> Result<fnet_interfaces_admin::Configuration, Errno> {
781        let max_multicast_solicitations = u16::try_from(value).map_err(|_| errno!(EINVAL))?;
782        let nud_config = fnet_interfaces_admin::NudConfiguration {
783            max_multicast_solicitations: Some(max_multicast_solicitations),
784            ..Default::default()
785        };
786        let mut config = fnet_interfaces_admin::Configuration::default();
787        match I::VERSION {
788            IpVersion::V4 => {
789                config.ipv4 = Some(fidl_fuchsia_net_interfaces_admin::Ipv4Configuration {
790                    arp: Some(fnet_interfaces_admin::ArpConfiguration {
791                        nud: Some(nud_config),
792                        ..Default::default()
793                    }),
794                    ..Default::default()
795                })
796            }
797            IpVersion::V6 => {
798                config.ipv6 = Some(fidl_fuchsia_net_interfaces_admin::Ipv6Configuration {
799                    ndp: Some(fnet_interfaces_admin::NdpConfiguration {
800                        nud: Some(nud_config),
801                        ..Default::default()
802                    }),
803                    ..Default::default()
804                })
805            }
806        }
807        Ok(config)
808    }
809
810    fn try_into_i32(config: fnet_interfaces_admin::Configuration) -> Result<i32, Errno> {
811        let max_multicast_solicitations = match I::VERSION {
812            IpVersion::V4 => config
813                .ipv4
814                .and_then(|ipv4| ipv4.arp)
815                .and_then(|arp| arp.nud)
816                .and_then(|nud| nud.max_multicast_solicitations)
817                .ok_or_else(|| {
818                    log_error!(
819                        "network interface config missing ipv4 arp max_multicast_solicitations"
820                    );
821                    errno!(EIO)
822                })?,
823            IpVersion::V6 => config
824                .ipv6
825                .and_then(|ipv6| ipv6.ndp)
826                .and_then(|ndp| ndp.nud)
827                .and_then(|nud| nud.max_multicast_solicitations)
828                .ok_or_else(|| {
829                    log_error!(
830                        "network interface config missing ipv6 ndp max_multicast_solicitations"
831                    );
832                    errno!(EIO)
833                })?,
834        };
835        Ok(i32::from(max_multicast_solicitations))
836    }
837}
838
839struct BaseReachableTimeMs<I: Ip> {
840    _marker: core::marker::PhantomData<I>,
841}
842
843impl<I: Ip> InterfaceConfig for BaseReachableTimeMs<I> {
844    fn try_from_i32(value: i32) -> Result<fnet_interfaces_admin::Configuration, Errno> {
845        let base_reachable_time = zx::Duration::<zx::BootTimeline>::from_millis(i64::from(value));
846        let nud_config = fnet_interfaces_admin::NudConfiguration {
847            base_reachable_time: Some(base_reachable_time.into_nanos()),
848            ..Default::default()
849        };
850        let mut config = fnet_interfaces_admin::Configuration::default();
851        match I::VERSION {
852            IpVersion::V4 => {
853                config.ipv4 = Some(fnet_interfaces_admin::Ipv4Configuration {
854                    arp: Some(fnet_interfaces_admin::ArpConfiguration {
855                        nud: Some(nud_config),
856                        ..Default::default()
857                    }),
858                    ..Default::default()
859                })
860            }
861            IpVersion::V6 => {
862                config.ipv6 = Some(fnet_interfaces_admin::Ipv6Configuration {
863                    ndp: Some(fnet_interfaces_admin::NdpConfiguration {
864                        nud: Some(nud_config),
865                        ..Default::default()
866                    }),
867                    ..Default::default()
868                })
869            }
870        }
871        Ok(config)
872    }
873
874    fn try_into_i32(config: fnet_interfaces_admin::Configuration) -> Result<i32, Errno> {
875        let base_reachable_time_ns = match I::VERSION {
876            IpVersion::V4 => config
877                .ipv4
878                .and_then(|ipv4| ipv4.arp)
879                .and_then(|arp| arp.nud)
880                .and_then(|nud| nud.base_reachable_time)
881                .ok_or_else(|| {
882                    log_error!("network interface config missing ipv4 arp base_reachable_time");
883                    errno!(EIO)
884                })?,
885            IpVersion::V6 => config
886                .ipv6
887                .and_then(|ipv6| ipv6.ndp)
888                .and_then(|ndp| ndp.nud)
889                .and_then(|nud| nud.base_reachable_time)
890                .ok_or_else(|| {
891                    log_error!("network interface config missing ipv6 ndp base_reachable_time");
892                    errno!(EIO)
893                })?,
894        };
895        Ok(i32::try_from(
896            zx::Duration::<zx::BootTimeline>::from_nanos(base_reachable_time_ns).into_millis(),
897        )
898        .map_err(|_| errno!(EIO))?)
899    }
900}
901
902struct Ipv6DadTransmits;
903
904impl InterfaceConfig for Ipv6DadTransmits {
905    fn try_from_i32(value: i32) -> Result<fidl_fuchsia_net_interfaces_admin::Configuration, Errno> {
906        let transmits = u16::try_from(value).map_err(|_| errno!(EINVAL))?;
907        Ok(fnet_interfaces_admin::Configuration {
908            ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
909                ndp: Some(fnet_interfaces_admin::NdpConfiguration {
910                    dad: Some(fnet_interfaces_admin::DadConfiguration {
911                        transmits: Some(transmits),
912                        ..Default::default()
913                    }),
914                    ..Default::default()
915                }),
916                ..Default::default()
917            }),
918            ..Default::default()
919        })
920    }
921
922    fn try_into_i32(
923        config: fidl_fuchsia_net_interfaces_admin::Configuration,
924    ) -> Result<i32, Errno> {
925        config
926            .ipv6
927            .and_then(|ipv6| ipv6.ndp)
928            .and_then(|ndp| ndp.dad)
929            .and_then(|dad| dad.transmits)
930            .map(i32::from)
931            .ok_or_else(|| {
932                log_error!("network interface config missing ipv6 ndp dad transmits");
933                errno!(EIO)
934            })
935    }
936}
937
938// Note that this has a different behavior than Linux, linux does not tell
939// whether a neighbor host variable is set by user or is learned from network.
940// The Fuchsia behavior is the same if the value is only set once during
941// initialization.
942struct RetransTimeMs<I: Ip> {
943    _marker: core::marker::PhantomData<I>,
944}
945
946impl<I: Ip> InterfaceConfig for RetransTimeMs<I> {
947    fn try_from_i32(value: i32) -> Result<fnet_interfaces_admin::Configuration, Errno> {
948        let retrans_timer = zx::Duration::<zx::BootTimeline>::from_millis(i64::from(value));
949        let nud_config = fnet_interfaces_admin::NudConfiguration {
950            retrans_timer: Some(retrans_timer.into_nanos()),
951            ..Default::default()
952        };
953        let mut config = fnet_interfaces_admin::Configuration::default();
954        match I::VERSION {
955            IpVersion::V4 => {
956                config.ipv4 = Some(fnet_interfaces_admin::Ipv4Configuration {
957                    arp: Some(fnet_interfaces_admin::ArpConfiguration {
958                        nud: Some(nud_config),
959                        ..Default::default()
960                    }),
961                    ..Default::default()
962                })
963            }
964            IpVersion::V6 => {
965                config.ipv6 = Some(fnet_interfaces_admin::Ipv6Configuration {
966                    ndp: Some(fnet_interfaces_admin::NdpConfiguration {
967                        nud: Some(nud_config),
968                        ..Default::default()
969                    }),
970                    ..Default::default()
971                })
972            }
973        }
974        Ok(config)
975    }
976
977    fn try_into_i32(config: fnet_interfaces_admin::Configuration) -> Result<i32, Errno> {
978        let retrans_timer_ns = match I::VERSION {
979            IpVersion::V4 => config
980                .ipv4
981                .and_then(|ipv4| ipv4.arp)
982                .and_then(|arp| arp.nud)
983                .and_then(|nud| nud.retrans_timer)
984                .ok_or_else(|| {
985                    log_error!("network interface config missing ipv4 arp retrans_timer");
986                    errno!(EIO)
987                })?,
988            IpVersion::V6 => config
989                .ipv6
990                .and_then(|ipv6| ipv6.ndp)
991                .and_then(|ndp| ndp.nud)
992                .and_then(|nud| nud.retrans_timer)
993                .ok_or_else(|| {
994                    log_error!("network interface config missing ipv6 ndp retrans_timer");
995                    errno!(EIO)
996                })?,
997        };
998        Ok(i32::try_from(
999            zx::Duration::<zx::BootTimeline>::from_nanos(retrans_timer_ns).into_millis(),
1000        )
1001        .map_err(|_| errno!(EIO))?)
1002    }
1003}
1004
1005struct UseTempAddr;
1006
1007impl InterfaceConfig for UseTempAddr {
1008    fn try_from_i32(value: i32) -> Result<fidl_fuchsia_net_interfaces_admin::Configuration, Errno> {
1009        // use_tempaddr - INTEGER
1010        // Preference for Privacy Extensions (RFC3041).
1011        // <= 0 : disable Privacy Extensions
1012        // == 1 : enable Privacy Extensions, but prefer public
1013        //      addresses over temporary addresses.
1014        // >  1 : enable Privacy Extensions and prefer temporary
1015        //      addresses over public addresses.
1016        //
1017        // Netstack only supports disable (<=0) or enable (>1). 1 is not a
1018        // sensible option. We will make it more strict by interpreting 1 as
1019        // >1.
1020        if value == 1 {
1021            log_warn!(
1022                "use_tempaddr=1 is not supported, treating it as enabled and we will prefer temporary addresses over public addresses"
1023            );
1024        }
1025        let use_tempaddr = value >= 1;
1026        Ok(fnet_interfaces_admin::Configuration {
1027            ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
1028                ndp: Some(fnet_interfaces_admin::NdpConfiguration {
1029                    slaac: Some(fnet_interfaces_admin::SlaacConfiguration {
1030                        temporary_address: Some(use_tempaddr),
1031                        ..Default::default()
1032                    }),
1033                    ..Default::default()
1034                }),
1035                ..Default::default()
1036            }),
1037            ..Default::default()
1038        })
1039    }
1040
1041    fn try_into_i32(
1042        config: fidl_fuchsia_net_interfaces_admin::Configuration,
1043    ) -> Result<i32, Errno> {
1044        config
1045            .ipv6
1046            .and_then(|ipv6| ipv6.ndp)
1047            .and_then(|ndp| ndp.slaac)
1048            .and_then(|slacc| slacc.temporary_address)
1049            // We deviate from Linux here by not remembering the original
1050            // value, this is acceptable for now and we should revisit if it
1051            // causes issues.
1052            .map(|use_tempaddr| if use_tempaddr { 2 } else { 0 })
1053            .ok_or_else(|| {
1054                log_error!("network interface config missing ipv6 ndp slacc temporary_address");
1055                errno!(EIO)
1056            })
1057    }
1058}
1059
1060struct AcceptRaDefrtr;
1061
1062impl InterfaceConfig for AcceptRaDefrtr {
1063    fn try_from_i32(value: i32) -> Result<fidl_fuchsia_net_interfaces_admin::Configuration, Errno> {
1064        Ok(fnet_interfaces_admin::Configuration {
1065            ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
1066                ndp: Some(fnet_interfaces_admin::NdpConfiguration {
1067                    route_discovery: Some(fnet_interfaces_admin::RouteDiscoveryConfiguration {
1068                        allow_default_route: Some(value != 0),
1069                        ..Default::default()
1070                    }),
1071                    ..Default::default()
1072                }),
1073                ..Default::default()
1074            }),
1075            ..Default::default()
1076        })
1077    }
1078
1079    fn try_into_i32(
1080        config: fidl_fuchsia_net_interfaces_admin::Configuration,
1081    ) -> Result<i32, Errno> {
1082        config
1083            .ipv6
1084            .and_then(|ipv6| ipv6.ndp)
1085            .and_then(|ndp| ndp.route_discovery)
1086            .and_then(|route_discovery| route_discovery.allow_default_route)
1087            .map(|allow_default_route| i32::from(allow_default_route))
1088            .ok_or_else(|| {
1089                log_error!(
1090                    "network interface config missing ipv6 ndp route_discovery allow_default_route"
1091                );
1092                errno!(EIO)
1093            })
1094    }
1095}
1096
1097pub struct TcpRmemFile;
1098
1099impl TcpRmemFile {
1100    pub fn new_node() -> impl FsNodeOps {
1101        BytesFile::new_node(Self)
1102    }
1103}
1104
1105impl BytesFileOps for TcpRmemFile {
1106    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1107        let mut params = std::str::from_utf8(&data)
1108            .map_err(|_| errno!(EINVAL))?
1109            .trim_ascii()
1110            .split_ascii_whitespace();
1111
1112        let mut next_u32 = || {
1113            params.next().ok_or_else(|| errno!(EINVAL))?.parse::<u32>().map_err(|_| errno!(EINVAL))
1114        };
1115
1116        let min = next_u32()?;
1117        let default = next_u32()?;
1118        let max = next_u32()?;
1119
1120        if params.next().is_some() {
1121            return error!(EINVAL);
1122        }
1123
1124        let control =
1125            connect_to_protocol_sync::<fnet_settings::ControlMarker>().map_err(|err| {
1126                log_error!(
1127                    "failed to connect to {}: {:?}",
1128                    fnet_settings::ControlMarker::PROTOCOL_NAME,
1129                    err
1130                );
1131                errno!(EIO)
1132            })?;
1133
1134        let tcp_settings = fnet_settings::Tcp {
1135            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1136                receive: Some(fnet_settings::SocketBufferSizeRange {
1137                    min: Some(min),
1138                    default: Some(default),
1139                    max: Some(max),
1140                    ..Default::default()
1141                }),
1142                ..Default::default()
1143            }),
1144            ..Default::default()
1145        };
1146
1147        control
1148            .update_tcp(&tcp_settings, zx::MonotonicInstant::INFINITE)
1149            .map_err(|err| {
1150                log_error!("failed to update tcp settings: {:?}", err);
1151                errno!(EIO)
1152            })?
1153            .map_err(map_update_error)?;
1154
1155        Ok(())
1156    }
1157
1158    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1159        let state = connect_to_protocol_sync::<fnet_settings::StateMarker>().map_err(|err| {
1160            log_error!(
1161                "failed to connect to {}: {:?}",
1162                fnet_settings::StateMarker::PROTOCOL_NAME,
1163                err
1164            );
1165            errno!(EIO)
1166        })?;
1167
1168        let tcp_settings = state.get_tcp(zx::MonotonicInstant::INFINITE).map_err(|err| {
1169            log_error!("failed to get tcp settings: {:?}", err);
1170            errno!(EIO)
1171        })?;
1172
1173        let receive_sizes =
1174            tcp_settings.buffer_sizes.and_then(|sizes| sizes.receive).ok_or_else(|| {
1175                log_error!("tcp settings missing receive buffer sizes");
1176                errno!(EIO)
1177            })?;
1178
1179        let min = receive_sizes.min.unwrap_or(0);
1180        let default = receive_sizes.default.unwrap_or(0);
1181        let max = receive_sizes.max.unwrap_or(0);
1182
1183        Ok(format!("{}\t{}\t{}\n", min, default, max).into_bytes().into())
1184    }
1185}
1186
1187pub struct TcpWmemFile;
1188
1189impl TcpWmemFile {
1190    pub fn new_node() -> impl FsNodeOps {
1191        BytesFile::new_node(Self)
1192    }
1193}
1194
1195impl BytesFileOps for TcpWmemFile {
1196    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1197        let mut params = std::str::from_utf8(&data)
1198            .map_err(|_| errno!(EINVAL))?
1199            .trim_ascii()
1200            .split_ascii_whitespace();
1201
1202        let mut next_u32 = || {
1203            params.next().ok_or_else(|| errno!(EINVAL))?.parse::<u32>().map_err(|_| errno!(EINVAL))
1204        };
1205
1206        let min = next_u32()?;
1207        let default = next_u32()?;
1208        let max = next_u32()?;
1209
1210        if params.next().is_some() {
1211            return error!(EINVAL);
1212        }
1213
1214        let control =
1215            connect_to_protocol_sync::<fnet_settings::ControlMarker>().map_err(|err| {
1216                log_error!(
1217                    "failed to connect to {}: {:?}",
1218                    fnet_settings::ControlMarker::PROTOCOL_NAME,
1219                    err
1220                );
1221                errno!(EIO)
1222            })?;
1223
1224        let tcp_settings = fnet_settings::Tcp {
1225            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1226                send: Some(fnet_settings::SocketBufferSizeRange {
1227                    min: Some(min),
1228                    default: Some(default),
1229                    max: Some(max),
1230                    ..Default::default()
1231                }),
1232                ..Default::default()
1233            }),
1234            ..Default::default()
1235        };
1236
1237        control
1238            .update_tcp(&tcp_settings, zx::MonotonicInstant::INFINITE)
1239            .map_err(|err| {
1240                log_error!("failed to update tcp settings: {:?}", err);
1241                errno!(EIO)
1242            })?
1243            .map_err(map_update_error)?;
1244
1245        Ok(())
1246    }
1247
1248    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1249        let state = connect_to_protocol_sync::<fnet_settings::StateMarker>().map_err(|err| {
1250            log_error!(
1251                "failed to connect to {}: {:?}",
1252                fnet_settings::StateMarker::PROTOCOL_NAME,
1253                err
1254            );
1255            errno!(EIO)
1256        })?;
1257
1258        let tcp_settings = state.get_tcp(zx::MonotonicInstant::INFINITE).map_err(|err| {
1259            log_error!("failed to get tcp settings: {:?}", err);
1260            errno!(EIO)
1261        })?;
1262
1263        let send_sizes =
1264            tcp_settings.buffer_sizes.and_then(|sizes| sizes.send).ok_or_else(|| {
1265                log_error!("tcp settings missing send buffer sizes");
1266                errno!(EIO)
1267            })?;
1268
1269        let min = send_sizes.min.unwrap_or(0);
1270        let default = send_sizes.default.unwrap_or(0);
1271        let max = send_sizes.max.unwrap_or(0);
1272
1273        Ok(format!("{}\t{}\t{}\n", min, default, max).into_bytes().into())
1274    }
1275}
1276
1277pub struct RmemMaxFile;
1278
1279impl RmemMaxFile {
1280    pub fn new_node() -> impl FsNodeOps {
1281        BytesFile::new_node(Self)
1282    }
1283}
1284
1285fn map_update_error(err: fnet_settings::UpdateError) -> Errno {
1286    match err {
1287        fnet_settings::UpdateError::IllegalZeroValue
1288        | fnet_settings::UpdateError::IllegalNegativeValue => errno!(EINVAL),
1289        fnet_settings::UpdateError::OutOfRange => errno!(ERANGE),
1290        fnet_settings::UpdateError::NotSupported => errno!(ENOTSUP),
1291        fnet_settings::UpdateError::__SourceBreaking { unknown_ordinal } => {
1292            log_error!("unknown error with ordinal: {unknown_ordinal}");
1293            errno!(EIO)
1294        }
1295    }
1296}
1297
1298impl BytesFileOps for RmemMaxFile {
1299    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1300        let max = std::str::from_utf8(&data)
1301            .map_err(|_| errno!(EINVAL))?
1302            .trim_ascii()
1303            .parse::<u32>()
1304            .map_err(|_| errno!(EINVAL))?;
1305
1306        let control =
1307            connect_to_protocol_sync::<fnet_settings::ControlMarker>().map_err(|err| {
1308                log_error!(
1309                    "failed to connect to {}: {:?}",
1310                    fnet_settings::ControlMarker::PROTOCOL_NAME,
1311                    err
1312                );
1313                errno!(EIO)
1314            })?;
1315
1316        let tcp_settings = fnet_settings::Tcp {
1317            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1318                receive: Some(fnet_settings::SocketBufferSizeRange {
1319                    max: Some(max),
1320                    ..Default::default()
1321                }),
1322                ..Default::default()
1323            }),
1324            ..Default::default()
1325        };
1326
1327        control
1328            .update_tcp(&tcp_settings, zx::MonotonicInstant::INFINITE)
1329            .map_err(|err| {
1330                log_error!("failed to update tcp settings: {:?}", err);
1331                errno!(EIO)
1332            })?
1333            .map_err(map_update_error)?;
1334
1335        let udp_settings = fnet_settings::Udp {
1336            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1337                receive: Some(fnet_settings::SocketBufferSizeRange {
1338                    max: Some(max),
1339                    ..Default::default()
1340                }),
1341                ..Default::default()
1342            }),
1343            ..Default::default()
1344        };
1345
1346        control
1347            .update_udp(&udp_settings, zx::MonotonicInstant::INFINITE)
1348            .map_err(|err| {
1349                log_error!("failed to update udp settings: {:?}", err);
1350                errno!(EIO)
1351            })?
1352            .map_err(map_update_error)?;
1353
1354        let icmp_settings = fnet_settings::Icmp {
1355            echo_buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1356                receive: Some(fnet_settings::SocketBufferSizeRange {
1357                    max: Some(max),
1358                    ..Default::default()
1359                }),
1360                ..Default::default()
1361            }),
1362            ..Default::default()
1363        };
1364
1365        control
1366            .update_icmp(&icmp_settings, zx::MonotonicInstant::INFINITE)
1367            .map_err(|err| {
1368                log_error!("failed to update icmp settings: {:?}", err);
1369                errno!(EIO)
1370            })?
1371            .map_err(map_update_error)?;
1372
1373        Ok(())
1374    }
1375
1376    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1377        let state = connect_to_protocol_sync::<fnet_settings::StateMarker>().map_err(|err| {
1378            log_error!(
1379                "failed to connect to {}: {:?}",
1380                fnet_settings::StateMarker::PROTOCOL_NAME,
1381                err
1382            );
1383            errno!(EIO)
1384        })?;
1385
1386        // Note: The three max values may have changed and not agree with each other.
1387        // Currently this is good enough to only get one of the max values.
1388        let tcp_settings = state.get_tcp(zx::MonotonicInstant::INFINITE).map_err(|err| {
1389            log_error!("failed to get tcp settings: {:?}", err);
1390            errno!(EIO)
1391        })?;
1392
1393        let max = tcp_settings
1394            .buffer_sizes
1395            .and_then(|sizes| sizes.receive)
1396            .and_then(|sizes| sizes.max)
1397            .ok_or_else(|| {
1398                log_error!("tcp settings missing receive buffer sizes");
1399                errno!(EIO)
1400            })?;
1401
1402        Ok(format!("{}\n", max).into_bytes().into())
1403    }
1404}
1405
1406pub struct WmemMaxFile;
1407
1408impl WmemMaxFile {
1409    pub fn new_node() -> impl FsNodeOps {
1410        BytesFile::new_node(Self)
1411    }
1412}
1413
1414impl BytesFileOps for WmemMaxFile {
1415    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1416        let max = std::str::from_utf8(&data)
1417            .map_err(|_| errno!(EINVAL))?
1418            .trim_ascii()
1419            .parse::<u32>()
1420            .map_err(|_| errno!(EINVAL))?;
1421
1422        let control =
1423            connect_to_protocol_sync::<fnet_settings::ControlMarker>().map_err(|err| {
1424                log_error!(
1425                    "failed to connect to {}: {:?}",
1426                    fnet_settings::ControlMarker::PROTOCOL_NAME,
1427                    err
1428                );
1429                errno!(EIO)
1430            })?;
1431
1432        let tcp_settings = fnet_settings::Tcp {
1433            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1434                send: Some(fnet_settings::SocketBufferSizeRange {
1435                    max: Some(max),
1436                    ..Default::default()
1437                }),
1438                ..Default::default()
1439            }),
1440            ..Default::default()
1441        };
1442
1443        control
1444            .update_tcp(&tcp_settings, zx::MonotonicInstant::INFINITE)
1445            .map_err(|err| {
1446                log_error!("failed to update tcp settings: {:?}", err);
1447                errno!(EIO)
1448            })?
1449            .map_err(map_update_error)?;
1450
1451        let udp_settings = fnet_settings::Udp {
1452            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1453                send: Some(fnet_settings::SocketBufferSizeRange {
1454                    max: Some(max),
1455                    ..Default::default()
1456                }),
1457                ..Default::default()
1458            }),
1459            ..Default::default()
1460        };
1461
1462        control
1463            .update_udp(&udp_settings, zx::MonotonicInstant::INFINITE)
1464            .map_err(|err| {
1465                log_error!("failed to update udp settings: {:?}", err);
1466                errno!(EIO)
1467            })?
1468            .map_err(map_update_error)?;
1469
1470        let icmp_settings = fnet_settings::Icmp {
1471            echo_buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1472                send: Some(fnet_settings::SocketBufferSizeRange {
1473                    max: Some(max),
1474                    ..Default::default()
1475                }),
1476                ..Default::default()
1477            }),
1478            ..Default::default()
1479        };
1480
1481        control
1482            .update_icmp(&icmp_settings, zx::MonotonicInstant::INFINITE)
1483            .map_err(|err| {
1484                log_error!("failed to update icmp settings: {:?}", err);
1485                errno!(EIO)
1486            })?
1487            .map_err(map_update_error)?;
1488
1489        Ok(())
1490    }
1491
1492    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1493        let state = connect_to_protocol_sync::<fnet_settings::StateMarker>().map_err(|err| {
1494            log_error!(
1495                "failed to connect to {}: {:?}",
1496                fnet_settings::StateMarker::PROTOCOL_NAME,
1497                err
1498            );
1499            errno!(EIO)
1500        })?;
1501
1502        // Note: The three max values may have changed and not agree with each other.
1503        // Currently this is good enough to only get one of the max values.
1504        let tcp_settings = state.get_tcp(zx::MonotonicInstant::INFINITE).map_err(|err| {
1505            log_error!("failed to get tcp settings: {:?}", err);
1506            errno!(EIO)
1507        })?;
1508
1509        let max = tcp_settings
1510            .buffer_sizes
1511            .and_then(|sizes| sizes.send)
1512            .and_then(|sizes| sizes.max)
1513            .ok_or_else(|| {
1514                log_error!("tcp settings missing send buffer sizes");
1515                errno!(EIO)
1516            })?;
1517
1518        Ok(format!("{}\n", max).into_bytes().into())
1519    }
1520}