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