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::task::{CurrentTask, RunState, WaiterRef};
17use starnix_core::vfs::socket::IfReqPtr;
18use starnix_core::vfs::{FileObject, FileObjectState, FileOps, call_fidl_and_await_close};
19use starnix_logging::{log_info, log_warn};
20use starnix_sync::{FileOpsCore, LockDepMutex, Locked, TunDevTunLock, Unlocked};
21use starnix_uapi::error;
22use starnix_uapi::errors::Errno;
23use std::num::NonZeroU64;
24use std::sync::Arc;
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    starnix_crypto::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(LockDepMutex<Option<DevTunInner>, TunDevTunLock>);
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 close(
335        self: Box<Self>,
336        _locked: &mut Locked<FileOpsCore>,
337        _file: &FileObjectState,
338        _current_task: &CurrentTask,
339    ) {
340        // Ensure that these TUN resources are available for reuse by explicitly `remove()`ing the
341        // port and waiting for that to be acknowledged, via the channel closing.
342        let Some(state) = self.0.lock().take() else {
343            return;
344        };
345        call_fidl_and_await_close(fnet_tun::PortSynchronousProxy::remove, &state.tun_port);
346    }
347
348    fn write(
349        &self,
350        _locked: &mut Locked<starnix_sync::FileOpsCore>,
351        _file: &FileObject,
352        _current_task: &CurrentTask,
353        _offset: usize,
354        _data: &mut dyn starnix_core::vfs::InputBuffer,
355    ) -> Result<usize, Errno> {
356        // TODO(https://fxbug.dev/332317144): Implement writing to a TUN/TAP
357        // device.
358        starnix_uapi::error!(EOPNOTSUPP)
359    }
360
361    fn read(
362        &self,
363        _locked: &mut Locked<starnix_sync::FileOpsCore>,
364        _file: &FileObject,
365        _current_task: &CurrentTask,
366        _offset: usize,
367        _data: &mut dyn starnix_core::vfs::OutputBuffer,
368    ) -> Result<usize, Errno> {
369        // TODO(https://fxbug.dev/332317144): Implement reading from a TUN/TAP
370        // device.
371        starnix_uapi::error!(EOPNOTSUPP)
372    }
373
374    fn ioctl(
375        &self,
376        _locked: &mut Locked<Unlocked>,
377        _file: &FileObject,
378        current_task: &CurrentTask,
379        request: u32,
380        arg: starnix_syscalls::SyscallArg,
381    ) -> Result<starnix_syscalls::SyscallResult, Errno> {
382        match request {
383            starnix_uapi::TUNSETIFF => {
384                security::check_tun_dev_create_access(current_task)?;
385                log_info!("handling TUNSETIFF for /dev/tun");
386                let user_addr = IfReqPtr::new(current_task, arg);
387                let in_ifreq = current_task.read_multi_arch_object(user_addr)?;
388
389                let name = in_ifreq.name_as_str()?.to_string();
390
391                let flags = in_ifreq.ifru_flags() as u32;
392
393                let iff_tun = flags & starnix_uapi::IFF_TUN != 0;
394                let iff_tap = flags & starnix_uapi::IFF_TAP != 0;
395                let iff_no_pi = flags & starnix_uapi::IFF_NO_PI != 0;
396
397                let kind = match (iff_tun, iff_tap) {
398                    (true, false) => DevKind::Tun,
399                    (false, true) => DevKind::Tap,
400                    _ => return starnix_uapi::error!(EINVAL),
401                };
402
403                let request = CreateTunRequest { name, kind, report_metadata: !iff_no_pi };
404
405                log_info!("adding /dev/tun interface {request:?}");
406
407                let (abort_handle, abort_registration) = futures::stream::AbortHandle::new_pair();
408                let abort_handle = Arc::new(abort_handle);
409
410                let CreateTunResponse { device, port, port_info, interface_id } = current_task
411                    .run_in_state(
412                        RunState::Waiter(WaiterRef::from_abort_handle(&abort_handle)),
413                        || {
414                            let mut executor = fasync::LocalExecutor::default();
415                            let tun_control = fuchsia_component::client::connect_to_protocol::<
416                                fnet_tun::ControlMarker,
417                            >()
418                            .map_err(|_| starnix_uapi::errno!(ENOENT))?;
419                            let installer = fuchsia_component::client::connect_to_protocol::<
420                                fnet_interfaces_admin::InstallerMarker,
421                            >()
422                            .map_err(|_| starnix_uapi::errno!(ENOENT))?;
423                            let mut worker = TunWorker { tun_control, installer };
424                            executor
425                                .run_singlethreaded(futures::stream::Abortable::new(
426                                    worker.handle_create_request(request),
427                                    abort_registration,
428                                ))
429                                .map_err(|futures::stream::Aborted| starnix_uapi::errno!(EINTR))?
430                        },
431                    )?;
432
433                *self.0.lock() = Some(DevTunInner {
434                    _tun_device: device.into_sync_proxy(),
435                    tun_port: port.into_sync_proxy(),
436                    _port_info: port_info,
437                    _interface_id: interface_id,
438                });
439
440                Ok(starnix_syscalls::SUCCESS)
441            }
442            _ => error!(ENOTTY),
443        }
444    }
445}