1use 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 octets[0] = octets[0] & !1;
63
64 octets[0] = octets[0] | 0b10;
69
70 fnet::MacAddress { octets }
71}
72
73#[derive(Debug)]
74struct CreateTunRequest {
75 name: String,
76 kind: DevKind,
77 report_metadata: bool,
79}
80
81struct 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 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 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 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 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 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 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 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}