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