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