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