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