1use 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, _) = ¤t_task.kernel().netstack_devices.initialized_and_wq;
43 if !initialized.load(Ordering::SeqCst) {
44 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) = ¤t_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 let _ = current_task.kernel().network_netlink();
113 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 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 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 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 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 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 *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
943struct 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 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 .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 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 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}