Skip to main content

starnix_modules_procfs/
sys_net.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::sync::atomic::Ordering;
6
7use fidl::endpoints::DiscoverableProtocolMarker as _;
8use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
9use fidl_fuchsia_net_root as fnet_root;
10use fidl_fuchsia_net_settings as fnet_settings;
11use fuchsia_component::client::connect_to_protocol_sync;
12use net_types::ip::{Ip, IpVersion, Ipv4, Ipv6};
13use netlink::{SysctlError, SysctlInterfaceSelector};
14use starnix_core::task::CurrentTask;
15use starnix_core::vfs::pseudo::simple_directory::SimpleDirectory;
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};
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};
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 = SimpleDirectory::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 = SimpleDirectory::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 = SimpleDirectory::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 = SimpleDirectory::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_config(
631    selector: SysctlInterfaceSelector,
632) -> Result<fidl_fuchsia_net_interfaces_admin::Configuration, Errno> {
633    match selector {
634        SysctlInterfaceSelector::All => {
635            log_warn!("getting config for all network interfaces is not supported");
636            Ok(Default::default())
637        }
638        SysctlInterfaceSelector::Default => {
639            let state =
640                connect_to_protocol_sync::<fnet_settings::StateMarker>().map_err(|err| {
641                    log_error!(
642                        "failed to connect to {}: {:?}",
643                        fnet_settings::StateMarker::PROTOCOL_NAME,
644                        err
645                    );
646                    errno!(EIO)
647                })?;
648            let config =
649                state.get_interface_defaults(zx::MonotonicInstant::INFINITE).map_err(|err| {
650                    log_error!("failed to get network interface defaults: {:?}", err);
651                    if err.is_closed() { errno!(ENODEV) } else { errno!(EIO) }
652                })?;
653            Ok(config)
654        }
655        SysctlInterfaceSelector::Id(id) => {
656            let root =
657                connect_to_protocol_sync::<fnet_root::InterfacesMarker>().map_err(|err| {
658                    log_error!(
659                        "failed to connect to {}: {:?}",
660                        fnet_root::InterfacesMarker::PROTOCOL_NAME,
661                        err
662                    );
663                    errno!(EIO)
664                })?;
665            let (control, server) = fidl::endpoints::create_sync_proxy();
666            root.get_admin(id.get(), server).map_err(|err| {
667                log_error!("failed to get network interface: {:?}", err);
668                errno!(EIO)
669            })?;
670            let config = control
671                .get_configuration(zx::MonotonicInstant::INFINITE)
672                .map_err(|err| {
673                    log_error!("failed to get network interface config: {:?}", err);
674                    if err.is_closed() { errno!(ENODEV) } else { errno!(EIO) }
675                })?
676                .map_err(|err| match err {
677                    fnet_interfaces_admin::ControlGetConfigurationError::__SourceBreaking {
678                        unknown_ordinal,
679                    } => {
680                        log_error!("unknown error with ordinal: {unknown_ordinal}");
681                        errno!(EIO)
682                    }
683                })?;
684            Ok(config)
685        }
686    }
687}
688
689struct DisableIpv6;
690
691impl InterfaceConfig for DisableIpv6 {
692    fn try_from_i32(value: i32) -> Result<fidl_fuchsia_net_interfaces_admin::Configuration, Errno> {
693        Ok(fidl_fuchsia_net_interfaces_admin::Configuration {
694            ipv6: Some(fidl_fuchsia_net_interfaces_admin::Ipv6Configuration {
695                enabled: Some(value == 0),
696                ..Default::default()
697            }),
698            ..Default::default()
699        })
700    }
701    fn try_into_i32(
702        config: fidl_fuchsia_net_interfaces_admin::Configuration,
703    ) -> Result<i32, Errno> {
704        let ipv6 = config.ipv6.ok_or_else(|| {
705            log_error!("network interface config missing ipv6");
706            errno!(EIO)
707        })?;
708        let enabled = ipv6.enabled.ok_or_else(|| {
709            log_error!("network interface config missing ipv6 enabled");
710            errno!(EIO)
711        })?;
712        Ok(i32::from(!enabled))
713    }
714}
715
716struct UcastSolicit<I: Ip> {
717    _marker: core::marker::PhantomData<I>,
718}
719
720impl<I: Ip> InterfaceConfig for UcastSolicit<I> {
721    fn try_from_i32(value: i32) -> Result<fnet_interfaces_admin::Configuration, Errno> {
722        let max_unicast_solicitations = u16::try_from(value).map_err(|_| errno!(EINVAL))?;
723        let nud_config = fnet_interfaces_admin::NudConfiguration {
724            max_unicast_solicitations: Some(max_unicast_solicitations),
725            ..Default::default()
726        };
727        let mut config = fnet_interfaces_admin::Configuration::default();
728        match I::VERSION {
729            IpVersion::V4 => {
730                config.ipv4 = Some(fidl_fuchsia_net_interfaces_admin::Ipv4Configuration {
731                    arp: Some(fnet_interfaces_admin::ArpConfiguration {
732                        nud: Some(nud_config),
733                        ..Default::default()
734                    }),
735                    ..Default::default()
736                })
737            }
738            IpVersion::V6 => {
739                config.ipv6 = Some(fidl_fuchsia_net_interfaces_admin::Ipv6Configuration {
740                    ndp: Some(fnet_interfaces_admin::NdpConfiguration {
741                        nud: Some(nud_config),
742                        ..Default::default()
743                    }),
744                    ..Default::default()
745                })
746            }
747        }
748        Ok(config)
749    }
750
751    fn try_into_i32(config: fnet_interfaces_admin::Configuration) -> Result<i32, Errno> {
752        let max_unicast_solicitations = match I::VERSION {
753            IpVersion::V4 => config
754                .ipv4
755                .and_then(|ipv4| ipv4.arp)
756                .and_then(|arp| arp.nud)
757                .and_then(|nud| nud.max_unicast_solicitations)
758                .ok_or_else(|| {
759                    log_error!(
760                        "network interface config missing ipv4 arp max_unicast_solicitations"
761                    );
762                    errno!(EIO)
763                })?,
764            IpVersion::V6 => config
765                .ipv6
766                .and_then(|ipv6| ipv6.ndp)
767                .and_then(|ndp| ndp.nud)
768                .and_then(|nud| nud.max_unicast_solicitations)
769                .ok_or_else(|| {
770                    log_error!(
771                        "network interface config missing ipv6 ndp max_unicast_solicitations"
772                    );
773                    errno!(EIO)
774                })?,
775        };
776        Ok(i32::from(max_unicast_solicitations))
777    }
778}
779
780struct McastResolicit<I: Ip> {
781    _marker: core::marker::PhantomData<I>,
782}
783
784impl<I: Ip> InterfaceConfig for McastResolicit<I> {
785    fn try_from_i32(value: i32) -> Result<fnet_interfaces_admin::Configuration, Errno> {
786        let max_multicast_solicitations = u16::try_from(value).map_err(|_| errno!(EINVAL))?;
787        let nud_config = fnet_interfaces_admin::NudConfiguration {
788            max_multicast_solicitations: Some(max_multicast_solicitations),
789            ..Default::default()
790        };
791        let mut config = fnet_interfaces_admin::Configuration::default();
792        match I::VERSION {
793            IpVersion::V4 => {
794                config.ipv4 = Some(fidl_fuchsia_net_interfaces_admin::Ipv4Configuration {
795                    arp: Some(fnet_interfaces_admin::ArpConfiguration {
796                        nud: Some(nud_config),
797                        ..Default::default()
798                    }),
799                    ..Default::default()
800                })
801            }
802            IpVersion::V6 => {
803                config.ipv6 = Some(fidl_fuchsia_net_interfaces_admin::Ipv6Configuration {
804                    ndp: Some(fnet_interfaces_admin::NdpConfiguration {
805                        nud: Some(nud_config),
806                        ..Default::default()
807                    }),
808                    ..Default::default()
809                })
810            }
811        }
812        Ok(config)
813    }
814
815    fn try_into_i32(config: fnet_interfaces_admin::Configuration) -> Result<i32, Errno> {
816        let max_multicast_solicitations = match I::VERSION {
817            IpVersion::V4 => config
818                .ipv4
819                .and_then(|ipv4| ipv4.arp)
820                .and_then(|arp| arp.nud)
821                .and_then(|nud| nud.max_multicast_solicitations)
822                .ok_or_else(|| {
823                    log_error!(
824                        "network interface config missing ipv4 arp max_multicast_solicitations"
825                    );
826                    errno!(EIO)
827                })?,
828            IpVersion::V6 => config
829                .ipv6
830                .and_then(|ipv6| ipv6.ndp)
831                .and_then(|ndp| ndp.nud)
832                .and_then(|nud| nud.max_multicast_solicitations)
833                .ok_or_else(|| {
834                    log_error!(
835                        "network interface config missing ipv6 ndp max_multicast_solicitations"
836                    );
837                    errno!(EIO)
838                })?,
839        };
840        Ok(i32::from(max_multicast_solicitations))
841    }
842}
843
844struct BaseReachableTimeMs<I: Ip> {
845    _marker: core::marker::PhantomData<I>,
846}
847
848impl<I: Ip> InterfaceConfig for BaseReachableTimeMs<I> {
849    fn try_from_i32(value: i32) -> Result<fnet_interfaces_admin::Configuration, Errno> {
850        let base_reachable_time = zx::Duration::<zx::BootTimeline>::from_millis(i64::from(value));
851        let nud_config = fnet_interfaces_admin::NudConfiguration {
852            base_reachable_time: Some(base_reachable_time.into_nanos()),
853            ..Default::default()
854        };
855        let mut config = fnet_interfaces_admin::Configuration::default();
856        match I::VERSION {
857            IpVersion::V4 => {
858                config.ipv4 = Some(fnet_interfaces_admin::Ipv4Configuration {
859                    arp: Some(fnet_interfaces_admin::ArpConfiguration {
860                        nud: Some(nud_config),
861                        ..Default::default()
862                    }),
863                    ..Default::default()
864                })
865            }
866            IpVersion::V6 => {
867                config.ipv6 = Some(fnet_interfaces_admin::Ipv6Configuration {
868                    ndp: Some(fnet_interfaces_admin::NdpConfiguration {
869                        nud: Some(nud_config),
870                        ..Default::default()
871                    }),
872                    ..Default::default()
873                })
874            }
875        }
876        Ok(config)
877    }
878
879    fn try_into_i32(config: fnet_interfaces_admin::Configuration) -> Result<i32, Errno> {
880        let base_reachable_time_ns = match I::VERSION {
881            IpVersion::V4 => config
882                .ipv4
883                .and_then(|ipv4| ipv4.arp)
884                .and_then(|arp| arp.nud)
885                .and_then(|nud| nud.base_reachable_time)
886                .ok_or_else(|| {
887                    log_error!("network interface config missing ipv4 arp base_reachable_time");
888                    errno!(EIO)
889                })?,
890            IpVersion::V6 => config
891                .ipv6
892                .and_then(|ipv6| ipv6.ndp)
893                .and_then(|ndp| ndp.nud)
894                .and_then(|nud| nud.base_reachable_time)
895                .ok_or_else(|| {
896                    log_error!("network interface config missing ipv6 ndp base_reachable_time");
897                    errno!(EIO)
898                })?,
899        };
900        Ok(i32::try_from(
901            zx::Duration::<zx::BootTimeline>::from_nanos(base_reachable_time_ns).into_millis(),
902        )
903        .map_err(|_| errno!(EIO))?)
904    }
905}
906
907struct Ipv6DadTransmits;
908
909impl InterfaceConfig for Ipv6DadTransmits {
910    fn try_from_i32(value: i32) -> Result<fidl_fuchsia_net_interfaces_admin::Configuration, Errno> {
911        let transmits = u16::try_from(value).map_err(|_| errno!(EINVAL))?;
912        Ok(fnet_interfaces_admin::Configuration {
913            ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
914                ndp: Some(fnet_interfaces_admin::NdpConfiguration {
915                    dad: Some(fnet_interfaces_admin::DadConfiguration {
916                        transmits: Some(transmits),
917                        ..Default::default()
918                    }),
919                    ..Default::default()
920                }),
921                ..Default::default()
922            }),
923            ..Default::default()
924        })
925    }
926
927    fn try_into_i32(
928        config: fidl_fuchsia_net_interfaces_admin::Configuration,
929    ) -> Result<i32, Errno> {
930        config
931            .ipv6
932            .and_then(|ipv6| ipv6.ndp)
933            .and_then(|ndp| ndp.dad)
934            .and_then(|dad| dad.transmits)
935            .map(i32::from)
936            .ok_or_else(|| {
937                log_error!("network interface config missing ipv6 ndp dad transmits");
938                errno!(EIO)
939            })
940    }
941}
942
943// Note that this has a different behavior than Linux, linux does not tell
944// whether a neighbor host variable is set by user or is learned from network.
945// The Fuchsia behavior is the same if the value is only set once during
946// initialization.
947struct RetransTimeMs<I: Ip> {
948    _marker: core::marker::PhantomData<I>,
949}
950
951impl<I: Ip> InterfaceConfig for RetransTimeMs<I> {
952    fn try_from_i32(value: i32) -> Result<fnet_interfaces_admin::Configuration, Errno> {
953        let retrans_timer = zx::Duration::<zx::BootTimeline>::from_millis(i64::from(value));
954        let nud_config = fnet_interfaces_admin::NudConfiguration {
955            retrans_timer: Some(retrans_timer.into_nanos()),
956            ..Default::default()
957        };
958        let mut config = fnet_interfaces_admin::Configuration::default();
959        match I::VERSION {
960            IpVersion::V4 => {
961                config.ipv4 = Some(fnet_interfaces_admin::Ipv4Configuration {
962                    arp: Some(fnet_interfaces_admin::ArpConfiguration {
963                        nud: Some(nud_config),
964                        ..Default::default()
965                    }),
966                    ..Default::default()
967                })
968            }
969            IpVersion::V6 => {
970                config.ipv6 = Some(fnet_interfaces_admin::Ipv6Configuration {
971                    ndp: Some(fnet_interfaces_admin::NdpConfiguration {
972                        nud: Some(nud_config),
973                        ..Default::default()
974                    }),
975                    ..Default::default()
976                })
977            }
978        }
979        Ok(config)
980    }
981
982    fn try_into_i32(config: fnet_interfaces_admin::Configuration) -> Result<i32, Errno> {
983        let retrans_timer_ns = match I::VERSION {
984            IpVersion::V4 => config
985                .ipv4
986                .and_then(|ipv4| ipv4.arp)
987                .and_then(|arp| arp.nud)
988                .and_then(|nud| nud.retrans_timer)
989                .ok_or_else(|| {
990                    log_error!("network interface config missing ipv4 arp retrans_timer");
991                    errno!(EIO)
992                })?,
993            IpVersion::V6 => config
994                .ipv6
995                .and_then(|ipv6| ipv6.ndp)
996                .and_then(|ndp| ndp.nud)
997                .and_then(|nud| nud.retrans_timer)
998                .ok_or_else(|| {
999                    log_error!("network interface config missing ipv6 ndp retrans_timer");
1000                    errno!(EIO)
1001                })?,
1002        };
1003        Ok(i32::try_from(
1004            zx::Duration::<zx::BootTimeline>::from_nanos(retrans_timer_ns).into_millis(),
1005        )
1006        .map_err(|_| errno!(EIO))?)
1007    }
1008}
1009
1010struct UseTempAddr;
1011
1012impl InterfaceConfig for UseTempAddr {
1013    fn try_from_i32(value: i32) -> Result<fidl_fuchsia_net_interfaces_admin::Configuration, Errno> {
1014        // use_tempaddr - INTEGER
1015        // Preference for Privacy Extensions (RFC3041).
1016        // <= 0 : disable Privacy Extensions
1017        // == 1 : enable Privacy Extensions, but prefer public
1018        //      addresses over temporary addresses.
1019        // >  1 : enable Privacy Extensions and prefer temporary
1020        //      addresses over public addresses.
1021        //
1022        // Netstack only supports disable (<=0) or enable (>1). 1 is not a
1023        // sensible option. We will make it more strict by interpreting 1 as
1024        // >1.
1025        if value == 1 {
1026            log_warn!(
1027                "use_tempaddr=1 is not supported, treating it as enabled and we will prefer temporary addresses over public addresses"
1028            );
1029        }
1030        let use_tempaddr = value >= 1;
1031        Ok(fnet_interfaces_admin::Configuration {
1032            ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
1033                ndp: Some(fnet_interfaces_admin::NdpConfiguration {
1034                    slaac: Some(fnet_interfaces_admin::SlaacConfiguration {
1035                        temporary_address: Some(use_tempaddr),
1036                        ..Default::default()
1037                    }),
1038                    ..Default::default()
1039                }),
1040                ..Default::default()
1041            }),
1042            ..Default::default()
1043        })
1044    }
1045
1046    fn try_into_i32(
1047        config: fidl_fuchsia_net_interfaces_admin::Configuration,
1048    ) -> Result<i32, Errno> {
1049        config
1050            .ipv6
1051            .and_then(|ipv6| ipv6.ndp)
1052            .and_then(|ndp| ndp.slaac)
1053            .and_then(|slacc| slacc.temporary_address)
1054            // We deviate from Linux here by not remembering the original
1055            // value, this is acceptable for now and we should revisit if it
1056            // causes issues.
1057            .map(|use_tempaddr| if use_tempaddr { 2 } else { 0 })
1058            .ok_or_else(|| {
1059                log_error!("network interface config missing ipv6 ndp slacc temporary_address");
1060                errno!(EIO)
1061            })
1062    }
1063}
1064
1065struct AcceptRaDefrtr;
1066
1067impl InterfaceConfig for AcceptRaDefrtr {
1068    fn try_from_i32(value: i32) -> Result<fidl_fuchsia_net_interfaces_admin::Configuration, Errno> {
1069        Ok(fnet_interfaces_admin::Configuration {
1070            ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
1071                ndp: Some(fnet_interfaces_admin::NdpConfiguration {
1072                    route_discovery: Some(fnet_interfaces_admin::RouteDiscoveryConfiguration {
1073                        allow_default_route: Some(value != 0),
1074                        ..Default::default()
1075                    }),
1076                    ..Default::default()
1077                }),
1078                ..Default::default()
1079            }),
1080            ..Default::default()
1081        })
1082    }
1083
1084    fn try_into_i32(
1085        config: fidl_fuchsia_net_interfaces_admin::Configuration,
1086    ) -> Result<i32, Errno> {
1087        config
1088            .ipv6
1089            .and_then(|ipv6| ipv6.ndp)
1090            .and_then(|ndp| ndp.route_discovery)
1091            .and_then(|route_discovery| route_discovery.allow_default_route)
1092            .map(|allow_default_route| i32::from(allow_default_route))
1093            .ok_or_else(|| {
1094                log_error!(
1095                    "network interface config missing ipv6 ndp route_discovery allow_default_route"
1096                );
1097                errno!(EIO)
1098            })
1099    }
1100}
1101
1102pub struct TcpRmemFile;
1103
1104impl TcpRmemFile {
1105    pub fn new_node() -> impl FsNodeOps {
1106        BytesFile::new_node(Self)
1107    }
1108}
1109
1110impl BytesFileOps for TcpRmemFile {
1111    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1112        let mut params = std::str::from_utf8(&data)
1113            .map_err(|_| errno!(EINVAL))?
1114            .trim_ascii()
1115            .split_ascii_whitespace();
1116
1117        let min = params
1118            .next()
1119            .ok_or_else(|| errno!(EINVAL))?
1120            .parse::<u32>()
1121            .map_err(|_| errno!(EINVAL))?;
1122        let default = params
1123            .next()
1124            .ok_or_else(|| errno!(EINVAL))?
1125            .parse::<u32>()
1126            .map_err(|_| errno!(EINVAL))?;
1127        let max = params
1128            .next()
1129            .ok_or_else(|| errno!(EINVAL))?
1130            .parse::<u32>()
1131            .map_err(|_| errno!(EINVAL))?;
1132
1133        if params.next().is_some() {
1134            return error!(EINVAL);
1135        }
1136
1137        let control =
1138            connect_to_protocol_sync::<fnet_settings::ControlMarker>().map_err(|err| {
1139                log_error!(
1140                    "failed to connect to {}: {:?}",
1141                    fnet_settings::ControlMarker::PROTOCOL_NAME,
1142                    err
1143                );
1144                errno!(EIO)
1145            })?;
1146
1147        let tcp_settings = fnet_settings::Tcp {
1148            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1149                receive: Some(fnet_settings::SocketBufferSizeRange {
1150                    min: Some(min),
1151                    default: Some(default),
1152                    max: Some(max),
1153                    ..Default::default()
1154                }),
1155                ..Default::default()
1156            }),
1157            ..Default::default()
1158        };
1159
1160        control
1161            .update_tcp(&tcp_settings, zx::MonotonicInstant::INFINITE)
1162            .map_err(|err| {
1163                log_error!("failed to update tcp settings: {:?}", err);
1164                errno!(EIO)
1165            })?
1166            .map_err(map_update_error)?;
1167
1168        Ok(())
1169    }
1170
1171    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1172        let state = connect_to_protocol_sync::<fnet_settings::StateMarker>().map_err(|err| {
1173            log_error!(
1174                "failed to connect to {}: {:?}",
1175                fnet_settings::StateMarker::PROTOCOL_NAME,
1176                err
1177            );
1178            errno!(EIO)
1179        })?;
1180
1181        let tcp_settings = state.get_tcp(zx::MonotonicInstant::INFINITE).map_err(|err| {
1182            log_error!("failed to get tcp settings: {:?}", err);
1183            errno!(EIO)
1184        })?;
1185
1186        let receive_sizes =
1187            tcp_settings.buffer_sizes.and_then(|sizes| sizes.receive).ok_or_else(|| {
1188                log_error!("tcp settings missing receive buffer sizes");
1189                errno!(EIO)
1190            })?;
1191
1192        let min = receive_sizes.min.unwrap_or(0);
1193        let default = receive_sizes.default.unwrap_or(0);
1194        let max = receive_sizes.max.unwrap_or(0);
1195
1196        Ok(format!("{}\t{}\t{}\n", min, default, max).into_bytes().into())
1197    }
1198}
1199
1200pub struct RmemMaxFile;
1201
1202impl RmemMaxFile {
1203    pub fn new_node() -> impl FsNodeOps {
1204        BytesFile::new_node(Self)
1205    }
1206}
1207
1208fn map_update_error(err: fnet_settings::UpdateError) -> Errno {
1209    match err {
1210        fnet_settings::UpdateError::IllegalZeroValue
1211        | fnet_settings::UpdateError::IllegalNegativeValue => errno!(EINVAL),
1212        fnet_settings::UpdateError::OutOfRange => errno!(ERANGE),
1213        fnet_settings::UpdateError::NotSupported => errno!(ENOTSUP),
1214        fnet_settings::UpdateError::__SourceBreaking { unknown_ordinal } => {
1215            log_error!("unknown error with ordinal: {unknown_ordinal}");
1216            errno!(EIO)
1217        }
1218    }
1219}
1220
1221impl BytesFileOps for RmemMaxFile {
1222    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1223        let max = std::str::from_utf8(&data)
1224            .map_err(|_| errno!(EINVAL))?
1225            .trim_ascii()
1226            .parse::<u32>()
1227            .map_err(|_| errno!(EINVAL))?;
1228
1229        let control =
1230            connect_to_protocol_sync::<fnet_settings::ControlMarker>().map_err(|err| {
1231                log_error!(
1232                    "failed to connect to {}: {:?}",
1233                    fnet_settings::ControlMarker::PROTOCOL_NAME,
1234                    err
1235                );
1236                errno!(EIO)
1237            })?;
1238
1239        let tcp_settings = fnet_settings::Tcp {
1240            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1241                receive: Some(fnet_settings::SocketBufferSizeRange {
1242                    max: Some(max),
1243                    ..Default::default()
1244                }),
1245                ..Default::default()
1246            }),
1247            ..Default::default()
1248        };
1249
1250        control
1251            .update_tcp(&tcp_settings, zx::MonotonicInstant::INFINITE)
1252            .map_err(|err| {
1253                log_error!("failed to update tcp settings: {:?}", err);
1254                errno!(EIO)
1255            })?
1256            .map_err(map_update_error)?;
1257
1258        let udp_settings = fnet_settings::Udp {
1259            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1260                receive: Some(fnet_settings::SocketBufferSizeRange {
1261                    max: Some(max),
1262                    ..Default::default()
1263                }),
1264                ..Default::default()
1265            }),
1266            ..Default::default()
1267        };
1268
1269        control
1270            .update_udp(&udp_settings, zx::MonotonicInstant::INFINITE)
1271            .map_err(|err| {
1272                log_error!("failed to update udp settings: {:?}", err);
1273                errno!(EIO)
1274            })?
1275            .map_err(map_update_error)?;
1276
1277        let icmp_settings = fnet_settings::Icmp {
1278            echo_buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1279                receive: Some(fnet_settings::SocketBufferSizeRange {
1280                    max: Some(max),
1281                    ..Default::default()
1282                }),
1283                ..Default::default()
1284            }),
1285            ..Default::default()
1286        };
1287
1288        control
1289            .update_icmp(&icmp_settings, zx::MonotonicInstant::INFINITE)
1290            .map_err(|err| {
1291                log_error!("failed to update icmp settings: {:?}", err);
1292                errno!(EIO)
1293            })?
1294            .map_err(map_update_error)?;
1295
1296        Ok(())
1297    }
1298
1299    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1300        let state = connect_to_protocol_sync::<fnet_settings::StateMarker>().map_err(|err| {
1301            log_error!(
1302                "failed to connect to {}: {:?}",
1303                fnet_settings::StateMarker::PROTOCOL_NAME,
1304                err
1305            );
1306            errno!(EIO)
1307        })?;
1308
1309        // Note: The three max values may have changed and not agree with each other.
1310        // Currently this is good enough to only get one of the max values.
1311        let tcp_settings = state.get_tcp(zx::MonotonicInstant::INFINITE).map_err(|err| {
1312            log_error!("failed to get tcp settings: {:?}", err);
1313            errno!(EIO)
1314        })?;
1315
1316        let max = tcp_settings
1317            .buffer_sizes
1318            .and_then(|sizes| sizes.receive)
1319            .and_then(|sizes| sizes.max)
1320            .ok_or_else(|| {
1321                log_error!("tcp settings missing receive buffer sizes");
1322                errno!(EIO)
1323            })?;
1324
1325        Ok(format!("{}\n", max).into_bytes().into())
1326    }
1327}
1328
1329pub struct WmemMaxFile;
1330
1331impl WmemMaxFile {
1332    pub fn new_node() -> impl FsNodeOps {
1333        BytesFile::new_node(Self)
1334    }
1335}
1336
1337impl BytesFileOps for WmemMaxFile {
1338    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1339        let max = std::str::from_utf8(&data)
1340            .map_err(|_| errno!(EINVAL))?
1341            .trim_ascii()
1342            .parse::<u32>()
1343            .map_err(|_| errno!(EINVAL))?;
1344
1345        let control =
1346            connect_to_protocol_sync::<fnet_settings::ControlMarker>().map_err(|err| {
1347                log_error!(
1348                    "failed to connect to {}: {:?}",
1349                    fnet_settings::ControlMarker::PROTOCOL_NAME,
1350                    err
1351                );
1352                errno!(EIO)
1353            })?;
1354
1355        let tcp_settings = fnet_settings::Tcp {
1356            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1357                send: Some(fnet_settings::SocketBufferSizeRange {
1358                    max: Some(max),
1359                    ..Default::default()
1360                }),
1361                ..Default::default()
1362            }),
1363            ..Default::default()
1364        };
1365
1366        control
1367            .update_tcp(&tcp_settings, zx::MonotonicInstant::INFINITE)
1368            .map_err(|err| {
1369                log_error!("failed to update tcp settings: {:?}", err);
1370                errno!(EIO)
1371            })?
1372            .map_err(map_update_error)?;
1373
1374        let udp_settings = fnet_settings::Udp {
1375            buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1376                send: Some(fnet_settings::SocketBufferSizeRange {
1377                    max: Some(max),
1378                    ..Default::default()
1379                }),
1380                ..Default::default()
1381            }),
1382            ..Default::default()
1383        };
1384
1385        control
1386            .update_udp(&udp_settings, zx::MonotonicInstant::INFINITE)
1387            .map_err(|err| {
1388                log_error!("failed to update udp settings: {:?}", err);
1389                errno!(EIO)
1390            })?
1391            .map_err(map_update_error)?;
1392
1393        let icmp_settings = fnet_settings::Icmp {
1394            echo_buffer_sizes: Some(fnet_settings::SocketBufferSizes {
1395                send: Some(fnet_settings::SocketBufferSizeRange {
1396                    max: Some(max),
1397                    ..Default::default()
1398                }),
1399                ..Default::default()
1400            }),
1401            ..Default::default()
1402        };
1403
1404        control
1405            .update_icmp(&icmp_settings, zx::MonotonicInstant::INFINITE)
1406            .map_err(|err| {
1407                log_error!("failed to update icmp settings: {:?}", err);
1408                errno!(EIO)
1409            })?
1410            .map_err(map_update_error)?;
1411
1412        Ok(())
1413    }
1414
1415    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1416        let state = connect_to_protocol_sync::<fnet_settings::StateMarker>().map_err(|err| {
1417            log_error!(
1418                "failed to connect to {}: {:?}",
1419                fnet_settings::StateMarker::PROTOCOL_NAME,
1420                err
1421            );
1422            errno!(EIO)
1423        })?;
1424
1425        // Note: The three max values may have changed and not agree with each other.
1426        // Currently this is good enough to only get one of the max values.
1427        let tcp_settings = state.get_tcp(zx::MonotonicInstant::INFINITE).map_err(|err| {
1428            log_error!("failed to get tcp settings: {:?}", err);
1429            errno!(EIO)
1430        })?;
1431
1432        let max = tcp_settings
1433            .buffer_sizes
1434            .and_then(|sizes| sizes.send)
1435            .and_then(|sizes| sizes.max)
1436            .ok_or_else(|| {
1437                log_error!("tcp settings missing send buffer sizes");
1438                errno!(EIO)
1439            })?;
1440
1441        Ok(format!("{}\n", max).into_bytes().into())
1442    }
1443}