1use fidl::endpoints::Proxy as _;
8use starnix_core::mm::MemoryAccessorExt;
9use starnix_core::security;
10use starnix_core::signals::RunState;
11use starnix_core::task::{CurrentTask, WaiterRef};
12use starnix_core::vfs::socket::IfReqPtr;
13use starnix_core::vfs::{FileObject, FileOps, default_ioctl};
14use starnix_logging::{log_info, log_warn};
15use starnix_sync::{Locked, Mutex, Unlocked};
16use starnix_uapi::errors::Errno;
17use std::num::NonZeroU64;
18use std::sync::Arc;
19use {
20 fidl_fuchsia_hardware_network as fhardware_network, fidl_fuchsia_net as fnet,
21 fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin,
22 fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext, fidl_fuchsia_net_tun as fnet_tun,
23 fuchsia_async as fasync,
24};
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 zx::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(Mutex<Option<DevTunInner>>);
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 write(
335 &self,
336 _locked: &mut Locked<starnix_sync::FileOpsCore>,
337 _file: &FileObject,
338 _current_task: &CurrentTask,
339 _offset: usize,
340 _data: &mut dyn starnix_core::vfs::InputBuffer,
341 ) -> Result<usize, Errno> {
342 starnix_uapi::error!(EOPNOTSUPP)
345 }
346
347 fn read(
348 &self,
349 _locked: &mut Locked<starnix_sync::FileOpsCore>,
350 _file: &FileObject,
351 _current_task: &CurrentTask,
352 _offset: usize,
353 _data: &mut dyn starnix_core::vfs::OutputBuffer,
354 ) -> Result<usize, Errno> {
355 starnix_uapi::error!(EOPNOTSUPP)
358 }
359
360 fn ioctl(
361 &self,
362 locked: &mut Locked<Unlocked>,
363 file: &FileObject,
364 current_task: &CurrentTask,
365 request: u32,
366 arg: starnix_syscalls::SyscallArg,
367 ) -> Result<starnix_syscalls::SyscallResult, Errno> {
368 match request {
369 starnix_uapi::TUNSETIFF => {
370 let mut inner = self.0.lock();
371
372 security::check_tun_dev_create_access(current_task)?;
373
374 log_info!("handling TUNSETIFF for /dev/tun");
375 let user_addr = IfReqPtr::new(current_task, arg);
376 let in_ifreq = current_task.read_multi_arch_object(user_addr)?;
377
378 let name = in_ifreq.name_as_str()?.to_string();
379
380 let flags = in_ifreq.ifru_flags() as u32;
381
382 let iff_tun = flags & starnix_uapi::IFF_TUN != 0;
383 let iff_tap = flags & starnix_uapi::IFF_TAP != 0;
384 let iff_no_pi = flags & starnix_uapi::IFF_NO_PI != 0;
385
386 let kind = match (iff_tun, iff_tap) {
387 (true, false) => DevKind::Tun,
388 (false, true) => DevKind::Tap,
389 _ => return starnix_uapi::error!(EINVAL),
390 };
391
392 let request = CreateTunRequest { name, kind, report_metadata: !iff_no_pi };
393
394 log_info!("adding /dev/tun interface {request:?}");
395
396 let (abort_handle, abort_registration) = futures::stream::AbortHandle::new_pair();
397 let abort_handle = Arc::new(abort_handle);
398
399 let CreateTunResponse { device, port, port_info, interface_id } = current_task
400 .run_in_state(
401 RunState::Waiter(WaiterRef::from_abort_handle(&abort_handle)),
402 || {
403 let mut executor = fasync::LocalExecutor::default();
404 let tun_control = fuchsia_component::client::connect_to_protocol::<
405 fnet_tun::ControlMarker,
406 >()
407 .map_err(|_| starnix_uapi::errno!(ENOENT))?;
408 let installer = fuchsia_component::client::connect_to_protocol::<
409 fnet_interfaces_admin::InstallerMarker,
410 >()
411 .map_err(|_| starnix_uapi::errno!(ENOENT))?;
412 let mut worker = TunWorker { tun_control, installer };
413 executor
414 .run_singlethreaded(futures::stream::Abortable::new(
415 worker.handle_create_request(request),
416 abort_registration,
417 ))
418 .map_err(|futures::stream::Aborted| starnix_uapi::errno!(EINTR))?
419 },
420 )?;
421
422 *inner = Some(DevTunInner {
423 _tun_device: device.into_sync_proxy(),
424 _tun_port: port.into_sync_proxy(),
425 _port_info: port_info,
426 _interface_id: interface_id,
427 });
428
429 Ok(starnix_syscalls::SUCCESS)
430 }
431 _ => default_ioctl(file, locked, current_task, request, arg),
432 }
433 }
434}