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    DirectoryEntryType, DirentSink, FileObject, FileOps, FsNode, FsNodeHandle, FsNodeOps, FsStr,
22    emit_dotdot, fileops_impl_directory, fileops_impl_noop_sync, fileops_impl_unbounded_seek,
23    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        node: &FsNode,
152        current_task: &CurrentTask,
153        name: &FsStr,
154    ) -> Result<FsNodeHandle, Errno> {
155        if get_netstack_device(current_task, name).is_some() {
156            let fs = 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        node: &FsNode,
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 = 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        node: &FsNode,
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 = 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        node: &FsNode,
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 = 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 min = params
1117            .next()
1118            .ok_or_else(|| errno!(EINVAL))?
1119            .parse::<u32>()
1120            .map_err(|_| errno!(EINVAL))?;
1121        let default = params
1122            .next()
1123            .ok_or_else(|| errno!(EINVAL))?
1124            .parse::<u32>()
1125            .map_err(|_| errno!(EINVAL))?;
1126        let max = params
1127            .next()
1128            .ok_or_else(|| errno!(EINVAL))?
1129            .parse::<u32>()
1130            .map_err(|_| errno!(EINVAL))?;
1131
1132        if params.next().is_some() {
1133            return error!(EINVAL);
1134        }
1135
1136        let control =
1137            connect_to_protocol_sync::<fnet_settings::ControlMarker>().map_err(|err| {
1138                log_error!(
1139                    "failed to connect to {}: {:?}",
1140                    fnet_settings::ControlMarker::PROTOCOL_NAME,
1141                    err
1142                );
1143                errno!(EIO)
1144            })?;
1145
1146        let tcp_settings = fnet_settings::Tcp {
1147            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1148                receive: Some(fnet_settings::SocketBufferSizeRange {
1149                    min: Some(min),
1150                    default: Some(default),
1151                    max: Some(max),
1152                    ..Default::default()
1153                }),
1154                ..Default::default()
1155            }),
1156            ..Default::default()
1157        };
1158
1159        control
1160            .update_tcp(&tcp_settings, zx::MonotonicInstant::INFINITE)
1161            .map_err(|err| {
1162                log_error!("failed to update tcp settings: {:?}", err);
1163                errno!(EIO)
1164            })?
1165            .map_err(map_update_error)?;
1166
1167        Ok(())
1168    }
1169
1170    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1171        let state = connect_to_protocol_sync::<fnet_settings::StateMarker>().map_err(|err| {
1172            log_error!(
1173                "failed to connect to {}: {:?}",
1174                fnet_settings::StateMarker::PROTOCOL_NAME,
1175                err
1176            );
1177            errno!(EIO)
1178        })?;
1179
1180        let tcp_settings = state.get_tcp(zx::MonotonicInstant::INFINITE).map_err(|err| {
1181            log_error!("failed to get tcp settings: {:?}", err);
1182            errno!(EIO)
1183        })?;
1184
1185        let receive_sizes =
1186            tcp_settings.buffer_sizes.and_then(|sizes| sizes.receive).ok_or_else(|| {
1187                log_error!("tcp settings missing receive buffer sizes");
1188                errno!(EIO)
1189            })?;
1190
1191        let min = receive_sizes.min.unwrap_or(0);
1192        let default = receive_sizes.default.unwrap_or(0);
1193        let max = receive_sizes.max.unwrap_or(0);
1194
1195        Ok(format!("{}\t{}\t{}\n", min, default, max).into_bytes().into())
1196    }
1197}
1198
1199pub struct RmemMaxFile;
1200
1201impl RmemMaxFile {
1202    pub fn new_node() -> impl FsNodeOps {
1203        BytesFile::new_node(Self)
1204    }
1205}
1206
1207fn map_update_error(err: fnet_settings::UpdateError) -> Errno {
1208    match err {
1209        fnet_settings::UpdateError::IllegalZeroValue
1210        | fnet_settings::UpdateError::IllegalNegativeValue => errno!(EINVAL),
1211        fnet_settings::UpdateError::OutOfRange => errno!(ERANGE),
1212        fnet_settings::UpdateError::NotSupported => errno!(ENOTSUP),
1213        fnet_settings::UpdateError::__SourceBreaking { unknown_ordinal } => {
1214            log_error!("unknown error with ordinal: {unknown_ordinal}");
1215            errno!(EIO)
1216        }
1217    }
1218}
1219
1220impl BytesFileOps for RmemMaxFile {
1221    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1222        let max = std::str::from_utf8(&data)
1223            .map_err(|_| errno!(EINVAL))?
1224            .trim_ascii()
1225            .parse::<u32>()
1226            .map_err(|_| errno!(EINVAL))?;
1227
1228        let control =
1229            connect_to_protocol_sync::<fnet_settings::ControlMarker>().map_err(|err| {
1230                log_error!(
1231                    "failed to connect to {}: {:?}",
1232                    fnet_settings::ControlMarker::PROTOCOL_NAME,
1233                    err
1234                );
1235                errno!(EIO)
1236            })?;
1237
1238        let tcp_settings = fnet_settings::Tcp {
1239            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1240                receive: Some(fnet_settings::SocketBufferSizeRange {
1241                    max: Some(max),
1242                    ..Default::default()
1243                }),
1244                ..Default::default()
1245            }),
1246            ..Default::default()
1247        };
1248
1249        control
1250            .update_tcp(&tcp_settings, zx::MonotonicInstant::INFINITE)
1251            .map_err(|err| {
1252                log_error!("failed to update tcp settings: {:?}", err);
1253                errno!(EIO)
1254            })?
1255            .map_err(map_update_error)?;
1256
1257        let udp_settings = fnet_settings::Udp {
1258            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1259                receive: Some(fnet_settings::SocketBufferSizeRange {
1260                    max: Some(max),
1261                    ..Default::default()
1262                }),
1263                ..Default::default()
1264            }),
1265            ..Default::default()
1266        };
1267
1268        control
1269            .update_udp(&udp_settings, zx::MonotonicInstant::INFINITE)
1270            .map_err(|err| {
1271                log_error!("failed to update udp settings: {:?}", err);
1272                errno!(EIO)
1273            })?
1274            .map_err(map_update_error)?;
1275
1276        let icmp_settings = fnet_settings::Icmp {
1277            echo_buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1278                receive: Some(fnet_settings::SocketBufferSizeRange {
1279                    max: Some(max),
1280                    ..Default::default()
1281                }),
1282                ..Default::default()
1283            }),
1284            ..Default::default()
1285        };
1286
1287        control
1288            .update_icmp(&icmp_settings, zx::MonotonicInstant::INFINITE)
1289            .map_err(|err| {
1290                log_error!("failed to update icmp settings: {:?}", err);
1291                errno!(EIO)
1292            })?
1293            .map_err(map_update_error)?;
1294
1295        Ok(())
1296    }
1297
1298    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1299        let state = connect_to_protocol_sync::<fnet_settings::StateMarker>().map_err(|err| {
1300            log_error!(
1301                "failed to connect to {}: {:?}",
1302                fnet_settings::StateMarker::PROTOCOL_NAME,
1303                err
1304            );
1305            errno!(EIO)
1306        })?;
1307
1308        // Note: The three max values may have changed and not agree with each other.
1309        // Currently this is good enough to only get one of the max values.
1310        let tcp_settings = state.get_tcp(zx::MonotonicInstant::INFINITE).map_err(|err| {
1311            log_error!("failed to get tcp settings: {:?}", err);
1312            errno!(EIO)
1313        })?;
1314
1315        let max = tcp_settings
1316            .buffer_sizes
1317            .and_then(|sizes| sizes.receive)
1318            .and_then(|sizes| sizes.max)
1319            .ok_or_else(|| {
1320                log_error!("tcp settings missing receive buffer sizes");
1321                errno!(EIO)
1322            })?;
1323
1324        Ok(format!("{}\n", max).into_bytes().into())
1325    }
1326}
1327
1328pub struct WmemMaxFile;
1329
1330impl WmemMaxFile {
1331    pub fn new_node() -> impl FsNodeOps {
1332        BytesFile::new_node(Self)
1333    }
1334}
1335
1336impl BytesFileOps for WmemMaxFile {
1337    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1338        let max = std::str::from_utf8(&data)
1339            .map_err(|_| errno!(EINVAL))?
1340            .trim_ascii()
1341            .parse::<u32>()
1342            .map_err(|_| errno!(EINVAL))?;
1343
1344        let control =
1345            connect_to_protocol_sync::<fnet_settings::ControlMarker>().map_err(|err| {
1346                log_error!(
1347                    "failed to connect to {}: {:?}",
1348                    fnet_settings::ControlMarker::PROTOCOL_NAME,
1349                    err
1350                );
1351                errno!(EIO)
1352            })?;
1353
1354        let tcp_settings = fnet_settings::Tcp {
1355            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1356                send: Some(fnet_settings::SocketBufferSizeRange {
1357                    max: Some(max),
1358                    ..Default::default()
1359                }),
1360                ..Default::default()
1361            }),
1362            ..Default::default()
1363        };
1364
1365        control
1366            .update_tcp(&tcp_settings, zx::MonotonicInstant::INFINITE)
1367            .map_err(|err| {
1368                log_error!("failed to update tcp settings: {:?}", err);
1369                errno!(EIO)
1370            })?
1371            .map_err(map_update_error)?;
1372
1373        let udp_settings = fnet_settings::Udp {
1374            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1375                send: Some(fnet_settings::SocketBufferSizeRange {
1376                    max: Some(max),
1377                    ..Default::default()
1378                }),
1379                ..Default::default()
1380            }),
1381            ..Default::default()
1382        };
1383
1384        control
1385            .update_udp(&udp_settings, zx::MonotonicInstant::INFINITE)
1386            .map_err(|err| {
1387                log_error!("failed to update udp settings: {:?}", err);
1388                errno!(EIO)
1389            })?
1390            .map_err(map_update_error)?;
1391
1392        let icmp_settings = fnet_settings::Icmp {
1393            echo_buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1394                send: Some(fnet_settings::SocketBufferSizeRange {
1395                    max: Some(max),
1396                    ..Default::default()
1397                }),
1398                ..Default::default()
1399            }),
1400            ..Default::default()
1401        };
1402
1403        control
1404            .update_icmp(&icmp_settings, zx::MonotonicInstant::INFINITE)
1405            .map_err(|err| {
1406                log_error!("failed to update icmp settings: {:?}", err);
1407                errno!(EIO)
1408            })?
1409            .map_err(map_update_error)?;
1410
1411        Ok(())
1412    }
1413
1414    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1415        let state = connect_to_protocol_sync::<fnet_settings::StateMarker>().map_err(|err| {
1416            log_error!(
1417                "failed to connect to {}: {:?}",
1418                fnet_settings::StateMarker::PROTOCOL_NAME,
1419                err
1420            );
1421            errno!(EIO)
1422        })?;
1423
1424        // Note: The three max values may have changed and not agree with each other.
1425        // Currently this is good enough to only get one of the max values.
1426        let tcp_settings = state.get_tcp(zx::MonotonicInstant::INFINITE).map_err(|err| {
1427            log_error!("failed to get tcp settings: {:?}", err);
1428            errno!(EIO)
1429        })?;
1430
1431        let max = tcp_settings
1432            .buffer_sizes
1433            .and_then(|sizes| sizes.send)
1434            .and_then(|sizes| sizes.max)
1435            .ok_or_else(|| {
1436                log_error!("tcp settings missing send buffer sizes");
1437                errno!(EIO)
1438            })?;
1439
1440        Ok(format!("{}\n", max).into_bytes().into())
1441    }
1442}