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