Skip to main content

starnix_modules_tun/
lib.rs

1// Copyright 2024 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
5//! This file contains code for creating and serving tun/tap devices.
6
7use fidl::endpoints::Proxy as _;
8use fidl_fuchsia_hardware_network as fhardware_network;
9use fidl_fuchsia_net as fnet;
10use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
11use fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext;
12use fidl_fuchsia_net_tun as fnet_tun;
13use fuchsia_async as fasync;
14use starnix_core::mm::MemoryAccessorExt;
15use starnix_core::security;
16use starnix_core::signals::RunState;
17use starnix_core::task::{CurrentTask, WaiterRef};
18use starnix_core::vfs::socket::IfReqPtr;
19use starnix_core::vfs::{
20    FileObject, FileObjectState, FileOps, call_fidl_and_await_close, default_ioctl,
21};
22use starnix_logging::{log_info, log_warn};
23use starnix_sync::{FileOpsCore, Locked, Mutex, Unlocked};
24use starnix_uapi::errors::Errno;
25use std::num::NonZeroU64;
26use std::sync::Arc;
27
28#[derive(Debug, Clone, Copy)]
29enum DevKind {
30    Tun,
31    Tap,
32}
33
34impl DevKind {
35    fn rx_types(&self) -> impl IntoIterator<Item = fhardware_network::FrameType> {
36        match self {
37            DevKind::Tun => itertools::Either::Left(
38                [fhardware_network::FrameType::Ipv4, fhardware_network::FrameType::Ipv6]
39                    .into_iter(),
40            ),
41            DevKind::Tap => {
42                itertools::Either::Right(std::iter::once(fhardware_network::FrameType::Ethernet))
43            }
44        }
45    }
46
47    fn tx_types(&self) -> impl IntoIterator<Item = fhardware_network::FrameTypeSupport> {
48        self.rx_types().into_iter().map(|frame_type| fhardware_network::FrameTypeSupport {
49            type_: frame_type,
50            features: 0,
51            supported_flags: fhardware_network::TxFlags::empty(),
52        })
53    }
54}
55
56fn random_mac() -> fnet::MacAddress {
57    let mut octets = [0u8; 6];
58    zx::cprng_draw(&mut octets[..]);
59    // Ensure the least-significant-bit of the first byte of the address is 0,
60    // indicating that it is a unicast address.
61    // https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast
62    octets[0] = octets[0] & !1;
63
64    // Ensure the second-least-significant bit of the first byte of the address
65    // is 1, indicating the address is locally administered (i.e. assigned by
66    // software and not by a device manufacturer).
67    // https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local_(U/L_bit)
68    octets[0] = octets[0] | 0b10;
69
70    fnet::MacAddress { octets }
71}
72
73#[derive(Debug)]
74struct CreateTunRequest {
75    name: String,
76    kind: DevKind,
77    // If true, will report frame metadata on receiving frames.
78    report_metadata: bool,
79}
80
81// Give back `ClientEnd`s so that clients can use synchronous proxies for
82// reading/writing operations on the files.
83struct CreateTunResponse {
84    device: fidl::endpoints::ClientEnd<fidl_fuchsia_net_tun::DeviceMarker>,
85    port: fidl::endpoints::ClientEnd<fidl_fuchsia_net_tun::PortMarker>,
86    port_info: fhardware_network::PortInfo,
87    interface_id: NonZeroU64,
88}
89
90const ARBITRARY_PORT_ID: u8 = 2;
91const ETHERNET_MTU: u32 = 1500;
92const MAX_ETHERNET_HEADER_SIZE: u32 = 18;
93
94macro_rules! errno_from_interfaces_admin_error {
95    ($err:expr) => {
96        match $err {
97            fnet_interfaces_ext::admin::TerminalError::Terminal(err) => match err {
98                fnet_interfaces_admin::InterfaceRemovedReason::DuplicateName => {
99                    starnix_uapi::errno!(
100                        EEXIST,
101                        "tried to create tuntap interface with duplicate name"
102                    )
103                }
104                fnet_interfaces_admin::InterfaceRemovedReason::PortAlreadyBound => {
105                    starnix_uapi::errno!(EBUSY, "tuntap port already bound to an interface")
106                }
107                fnet_interfaces_admin::InterfaceRemovedReason::BadPort => {
108                    starnix_uapi::errno!(ENOENT, "tried to create tuntap interface from bad port")
109                }
110                fnet_interfaces_admin::InterfaceRemovedReason::PortClosed => {
111                    starnix_uapi::errno!(
112                        ENOENT,
113                        "tried to create tuntap interface from closed port"
114                    )
115                }
116                fnet_interfaces_admin::InterfaceRemovedReason::User => {
117                    starnix_uapi::errno!(
118                        ENOENT,
119                        "tuntap interface was removed out from under starnix"
120                    )
121                }
122                fnet_interfaces_admin::InterfaceRemovedReasonUnknown!() => {
123                    starnix_uapi::errno!(ENOENT, "unknown interface removed reason")
124                }
125            },
126            fnet_interfaces_ext::admin::TerminalError::Fidl(e) => {
127                starnix_uapi::errno!(ENOENT, format!("interfaces admin FIDL error: {e:?}"))
128            }
129        }
130    };
131}
132
133struct TunWorker {
134    tun_control: fidl_fuchsia_net_tun::ControlProxy,
135    installer: fidl_fuchsia_net_interfaces_admin::InstallerProxy,
136}
137
138impl TunWorker {
139    async fn handle_create_request(
140        &mut self,
141        request: CreateTunRequest,
142    ) -> Result<CreateTunResponse, Errno> {
143        let CreateTunRequest { name, kind, report_metadata } = request;
144        if report_metadata {
145            log_warn!(
146                "TODO(https://fxbug.dev/332317144): frame-metadata reporting for
147                 tuntap interfaces in starnix is not implemented yet"
148            );
149        }
150        let report_metadata = false;
151
152        let Self { tun_control, installer } = self;
153
154        let (tun_device, server_end) =
155            fidl::endpoints::create_endpoints::<fnet_tun::DeviceMarker>();
156        tun_control
157            .create_device(
158                &fnet_tun::DeviceConfig {
159                    blocking: Some(false),
160                    base: Some(fnet_tun::BaseDeviceConfig {
161                        report_metadata: Some(report_metadata),
162                        min_tx_buffer_length: None,
163                        min_rx_buffer_length: None,
164                        ..Default::default()
165                    }),
166                    ..Default::default()
167                },
168                server_end,
169            )
170            .map_err(|e| {
171                starnix_uapi::errno!(
172                    ENOENT,
173                    format!("creating fuchsia.net.tun Device failed: {e:?}")
174                )
175            })?;
176
177        let (tun_port, server_end) = fidl::endpoints::create_endpoints::<fnet_tun::PortMarker>();
178
179        let tun_device = tun_device.into_proxy();
180        tun_device
181            .add_port(
182                &fnet_tun::DevicePortConfig {
183                    base: Some(fnet_tun::BasePortConfig {
184                        id: Some(ARBITRARY_PORT_ID),
185                        // Even though this field is named `mtu`, it's actually
186                        // the maximum frame size of whatever layer the
187                        // interface operates at. So if we want to get to the
188                        // typical 1500 MTU that we usually have above the
189                        // Ethernet layer, for TAP devices we need to add space
190                        // to account for the Ethernet header itself.
191                        mtu: Some(
192                            ETHERNET_MTU
193                                + match kind {
194                                    DevKind::Tun => 0,
195                                    DevKind::Tap => MAX_ETHERNET_HEADER_SIZE,
196                                },
197                        ),
198                        rx_types: Some(kind.rx_types().into_iter().collect::<Vec<_>>()),
199                        tx_types: Some(kind.tx_types().into_iter().collect::<Vec<_>>()),
200                        port_class: None,
201                        ..Default::default()
202                    }),
203                    online: Some(true),
204                    mac: match kind {
205                        DevKind::Tun => None,
206                        DevKind::Tap => Some(random_mac()),
207                    },
208                    ..Default::default()
209                },
210                server_end,
211            )
212            .map_err(|e| {
213                starnix_uapi::errno!(ENOENT, format!("adding fuchsia.net.tun Port failed, {e:?}"))
214            })?;
215
216        let tun_port = tun_port.into_proxy();
217        let (hw_port, server_end) =
218            fidl::endpoints::create_endpoints::<fhardware_network::PortMarker>();
219        tun_port.get_port(server_end).map_err(|e| {
220            starnix_uapi::errno!(
221                ENOENT,
222                format!("getting fuchsia.hardware.networkn Port failed: {e:?}")
223            )
224        })?;
225        let hw_port = hw_port.into_proxy();
226        let port_info = hw_port.get_info().await.map_err(|e| {
227            starnix_uapi::errno!(
228                ENOENT,
229                format!("getting fuchsia.hardware.network PortInfo failed: {e:?}")
230            )
231        })?;
232
233        let (hw_device, server_end) =
234            fidl::endpoints::create_endpoints::<fhardware_network::DeviceMarker>();
235        tun_device.get_device(server_end).map_err(|e| {
236            starnix_uapi::errno!(
237                ENOENT,
238                format!("getting fuchsia.hardware.network Device failed: {e:?}")
239            )
240        })?;
241
242        let (device_control, server_end) =
243            fidl::endpoints::create_endpoints::<fnet_interfaces_admin::DeviceControlMarker>();
244        installer.install_device(hw_device, server_end).map_err(|e| {
245            starnix_uapi::errno!(
246                ENOENT,
247                format!("installing fuchsia.hardware.network Device failed: {e:?}")
248            )
249        })?;
250
251        let (interface_control, server_end) =
252            fidl::endpoints::create_endpoints::<fnet_interfaces_admin::ControlMarker>();
253        let device_control = device_control.into_proxy();
254        device_control
255            .create_interface(
256                &port_info
257                    .id
258                    .ok_or_else(|| starnix_uapi::errno!(ENOENT, "got PortInfo with no ID"))?,
259                server_end,
260                fnet_interfaces_admin::Options {
261                    name: Some(name.clone()),
262                    metric: None,
263                    ..Default::default()
264                },
265            )
266            .map_err(|e| {
267                starnix_uapi::errno!(
268                    ENOENT,
269                    format!("creating fuchsia.net.interfaces.admin Control failed: {e:?}")
270                )
271            })?;
272
273        let interface_control = interface_control.into_proxy();
274        let interface_control = fnet_interfaces_ext::admin::Control::new(interface_control);
275
276        // Get the NICID that the netstack allocates for this interface. This
277        // serves as a way to wait for the interface to be successfully
278        // installed, verifying that there's no duplicate-name clash.
279        let interface_id = interface_control
280            .get_id()
281            .await
282            .map_err(|err| errno_from_interfaces_admin_error!(err))?;
283        let interface_id = NonZeroU64::new(interface_id).expect("interface IDs must be nonzero");
284        let _enabled: bool = interface_control
285            .enable()
286            .await
287            .map_err(|err| errno_from_interfaces_admin_error!(err))?
288            .map_err(|err: fnet_interfaces_admin::ControlEnableError| {
289                starnix_uapi::errno!(
290                    ENOENT,
291                    format!("enabling fuchsia.net.interfaces.admin Control failed: {err:?}")
292                )
293            })?;
294
295        let tun_device =
296            tun_device.into_client_end().expect("should not have cloned tun_device proxy");
297        let tun_port = tun_port.into_client_end().expect("should not have cloned tun_port proxy");
298
299        // Detach the fnet_interfaces_admin DeviceControl to avoid it being
300        // uninstalled once we drop the proxy. NB: we don't want to do this
301        // until we're done doing any async work so that we clean up properly if
302        // we're interrupted.
303        device_control.detach().map_err(|e| {
304            starnix_uapi::errno!(
305                ENOENT,
306                format!("detaching fuchsia.net.interfaces.admin DeviceControl failed: {e:?}")
307            )
308        })?;
309
310        // Same for the fnet_interfaces_admin Control.
311        interface_control.detach().map_err(|e| {
312            starnix_uapi::errno!(
313                ENOENT,
314                format!("detaching fuchsia.net.interfaces.admin Control failed: {e:?}")
315            )
316        })?;
317
318        Ok(CreateTunResponse { device: tun_device, port: tun_port, port_info, interface_id })
319    }
320}
321
322#[derive(Default)]
323pub struct DevTun(Mutex<Option<DevTunInner>>);
324
325struct DevTunInner {
326    _tun_device: fnet_tun::DeviceSynchronousProxy,
327    tun_port: fnet_tun::PortSynchronousProxy,
328    _port_info: fhardware_network::PortInfo,
329    _interface_id: NonZeroU64,
330}
331
332impl FileOps for DevTun {
333    starnix_core::fileops_impl_nonseekable!();
334    starnix_core::fileops_impl_noop_sync!();
335
336    fn close(
337        self: Box<Self>,
338        _locked: &mut Locked<FileOpsCore>,
339        _file: &FileObjectState,
340        _current_task: &CurrentTask,
341    ) {
342        // Ensure that these TUN resources are available for reuse by explicitly `remove()`ing the
343        // port and waiting for that to be acknowledged, via the channel closing.
344        let Some(state) = self.0.lock().take() else {
345            return;
346        };
347        call_fidl_and_await_close(fnet_tun::PortSynchronousProxy::remove, &state.tun_port);
348    }
349
350    fn write(
351        &self,
352        _locked: &mut Locked<starnix_sync::FileOpsCore>,
353        _file: &FileObject,
354        _current_task: &CurrentTask,
355        _offset: usize,
356        _data: &mut dyn starnix_core::vfs::InputBuffer,
357    ) -> Result<usize, Errno> {
358        // TODO(https://fxbug.dev/332317144): Implement writing to a TUN/TAP
359        // device.
360        starnix_uapi::error!(EOPNOTSUPP)
361    }
362
363    fn read(
364        &self,
365        _locked: &mut Locked<starnix_sync::FileOpsCore>,
366        _file: &FileObject,
367        _current_task: &CurrentTask,
368        _offset: usize,
369        _data: &mut dyn starnix_core::vfs::OutputBuffer,
370    ) -> Result<usize, Errno> {
371        // TODO(https://fxbug.dev/332317144): Implement reading from a TUN/TAP
372        // device.
373        starnix_uapi::error!(EOPNOTSUPP)
374    }
375
376    fn ioctl(
377        &self,
378        locked: &mut Locked<Unlocked>,
379        file: &FileObject,
380        current_task: &CurrentTask,
381        request: u32,
382        arg: starnix_syscalls::SyscallArg,
383    ) -> Result<starnix_syscalls::SyscallResult, Errno> {
384        match request {
385            starnix_uapi::TUNSETIFF => {
386                let mut inner = self.0.lock();
387
388                security::check_tun_dev_create_access(current_task)?;
389
390                log_info!("handling TUNSETIFF for /dev/tun");
391                let user_addr = IfReqPtr::new(current_task, arg);
392                let in_ifreq = current_task.read_multi_arch_object(user_addr)?;
393
394                let name = in_ifreq.name_as_str()?.to_string();
395
396                let flags = in_ifreq.ifru_flags() as u32;
397
398                let iff_tun = flags & starnix_uapi::IFF_TUN != 0;
399                let iff_tap = flags & starnix_uapi::IFF_TAP != 0;
400                let iff_no_pi = flags & starnix_uapi::IFF_NO_PI != 0;
401
402                let kind = match (iff_tun, iff_tap) {
403                    (true, false) => DevKind::Tun,
404                    (false, true) => DevKind::Tap,
405                    _ => return starnix_uapi::error!(EINVAL),
406                };
407
408                let request = CreateTunRequest { name, kind, report_metadata: !iff_no_pi };
409
410                log_info!("adding /dev/tun interface {request:?}");
411
412                let (abort_handle, abort_registration) = futures::stream::AbortHandle::new_pair();
413                let abort_handle = Arc::new(abort_handle);
414
415                let CreateTunResponse { device, port, port_info, interface_id } = current_task
416                    .run_in_state(
417                        RunState::Waiter(WaiterRef::from_abort_handle(&abort_handle)),
418                        || {
419                            let mut executor = fasync::LocalExecutor::default();
420                            let tun_control = fuchsia_component::client::connect_to_protocol::<
421                                fnet_tun::ControlMarker,
422                            >()
423                            .map_err(|_| starnix_uapi::errno!(ENOENT))?;
424                            let installer = fuchsia_component::client::connect_to_protocol::<
425                                fnet_interfaces_admin::InstallerMarker,
426                            >()
427                            .map_err(|_| starnix_uapi::errno!(ENOENT))?;
428                            let mut worker = TunWorker { tun_control, installer };
429                            executor
430                                .run_singlethreaded(futures::stream::Abortable::new(
431                                    worker.handle_create_request(request),
432                                    abort_registration,
433                                ))
434                                .map_err(|futures::stream::Aborted| starnix_uapi::errno!(EINTR))?
435                        },
436                    )?;
437
438                *inner = Some(DevTunInner {
439                    _tun_device: device.into_sync_proxy(),
440                    tun_port: port.into_sync_proxy(),
441                    _port_info: port_info,
442                    _interface_id: interface_id,
443                });
444
445                Ok(starnix_syscalls::SUCCESS)
446            }
447            _ => default_ioctl(file, locked, current_task, request, arg),
448        }
449    }
450}