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::{LockDepMutex, TunDevTunLock};
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(self: Box<Self>, _file: &FileObjectState, _current_task: &CurrentTask) {
335 let Some(state) = self.0.lock().take() else {
338 return;
339 };
340 call_fidl_and_await_close(fnet_tun::PortSynchronousProxy::remove, &state.tun_port);
341 }
342
343 fn write(
344 &self,
345 _file: &FileObject,
346 _current_task: &CurrentTask,
347 _offset: usize,
348 _data: &mut dyn starnix_core::vfs::InputBuffer,
349 ) -> Result<usize, Errno> {
350 starnix_uapi::error!(EOPNOTSUPP)
353 }
354
355 fn read(
356 &self,
357 _file: &FileObject,
358 _current_task: &CurrentTask,
359 _offset: usize,
360 _data: &mut dyn starnix_core::vfs::OutputBuffer,
361 ) -> Result<usize, Errno> {
362 starnix_uapi::error!(EOPNOTSUPP)
365 }
366
367 fn ioctl(
368 &self,
369 _file: &FileObject,
370 current_task: &CurrentTask,
371 request: u32,
372 arg: starnix_syscalls::SyscallArg,
373 ) -> Result<starnix_syscalls::SyscallResult, Errno> {
374 match request {
375 starnix_uapi::TUNSETIFF => {
376 security::check_tun_dev_create_access(current_task)?;
377 log_info!("handling TUNSETIFF for /dev/tun");
378 let user_addr = IfReqPtr::new(current_task, arg);
379 let in_ifreq = current_task.read_multi_arch_object(user_addr)?;
380
381 let name = in_ifreq.name_as_str()?.to_string();
382
383 let flags = in_ifreq.ifru_flags() as u32;
384
385 let iff_tun = flags & starnix_uapi::IFF_TUN != 0;
386 let iff_tap = flags & starnix_uapi::IFF_TAP != 0;
387 let iff_no_pi = flags & starnix_uapi::IFF_NO_PI != 0;
388
389 let kind = match (iff_tun, iff_tap) {
390 (true, false) => DevKind::Tun,
391 (false, true) => DevKind::Tap,
392 _ => return starnix_uapi::error!(EINVAL),
393 };
394
395 let request = CreateTunRequest { name, kind, report_metadata: !iff_no_pi };
396
397 log_info!("adding /dev/tun interface {request:?}");
398
399 let (abort_handle, abort_registration) = futures::stream::AbortHandle::new_pair();
400 let abort_handle = Arc::new(abort_handle);
401
402 let CreateTunResponse { device, port, port_info, interface_id } = current_task
403 .run_in_state(
404 RunState::Waiter(WaiterRef::from_abort_handle(&abort_handle)),
405 || {
406 let mut executor = fasync::LocalExecutor::default();
407 let tun_control = fuchsia_component::client::connect_to_protocol::<
408 fnet_tun::ControlMarker,
409 >()
410 .map_err(|_| starnix_uapi::errno!(ENOENT))?;
411 let installer = fuchsia_component::client::connect_to_protocol::<
412 fnet_interfaces_admin::InstallerMarker,
413 >()
414 .map_err(|_| starnix_uapi::errno!(ENOENT))?;
415 let mut worker = TunWorker { tun_control, installer };
416 executor
417 .run_singlethreaded(futures::stream::Abortable::new(
418 worker.handle_create_request(request),
419 abort_registration,
420 ))
421 .map_err(|futures::stream::Aborted| starnix_uapi::errno!(EINTR))?
422 },
423 )?;
424
425 *self.0.lock() = Some(DevTunInner {
426 _tun_device: device.into_sync_proxy(),
427 tun_port: port.into_sync_proxy(),
428 _port_info: port_info,
429 _interface_id: interface_id,
430 });
431
432 Ok(starnix_syscalls::SUCCESS)
433 }
434 _ => error!(ENOTTY),
435 }
436 }
437}