Skip to content

Commit 3205ad8

Browse files
committed
net: implement a backend for tap devices
Implement a new backend that allows network interfaces to use a tap device on the host to read/write frames. Signed-off-by: Sergio Lopez <[email protected]>
1 parent f0f3a61 commit 3205ad8

File tree

7 files changed

+202
-0
lines changed

7 files changed

+202
-0
lines changed

include/libkrun.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,36 @@ int32_t krun_add_net_unixgram(uint32_t ctx_id,
370370
uint32_t features,
371371
uint32_t flags);
372372

373+
/**
374+
* Adds an independent virtio-net device with the tap backend.
375+
* Call to this function disables TSI backend.
376+
377+
* The "krun_add_net_*" functions can be called multiple times for
378+
* adding multiple virtio-net devices. In the guest the interfaces
379+
* will appear in the same order as they are added (that is, the
380+
* first added interface will be "eth0", the second "eth1"...)
381+
*
382+
* Arguments:
383+
* "ctx_id" - the configuration context ID.
384+
* "c_tap_name" - a null-terminated string representing the tap
385+
* device name.
386+
* "c_mac" - MAC address as an array of 6 uint8_t entries.
387+
* "features" - virtio-net features for the network interface.
388+
* "flags" - generic flags for the network interface.
389+
*
390+
* Notes:
391+
* If no network devices are added, networking uses the TSI backend.
392+
* This function should be called before krun_set_port_map.
393+
*
394+
* Returns:
395+
* Zero on success or a negative error number on failure.
396+
*/
397+
int32_t krun_add_net_tap(uint32_t ctx_id,
398+
char *c_tap_name,
399+
uint8_t *const c_mac,
400+
uint32_t features,
401+
uint32_t flags);
402+
373403
/**
374404
* DEPRECATED. Use krun_add_net_unixstream instead.
375405
*

src/devices/src/virtio/net/backend.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::io;
12
use std::os::fd::RawFd;
23

34
#[allow(dead_code)]
@@ -7,6 +8,11 @@ pub enum ConnectError {
78
CreateSocket(nix::Error),
89
Binding(nix::Error),
910
SendingMagic(nix::Error),
11+
// Tap backend errors.
12+
OpenNetTun(io::Error),
13+
TunSetIff(io::Error),
14+
TunSetVnetHdrSz(io::Error),
15+
TunSetOffload(io::Error),
1016
}
1117

1218
#[allow(dead_code)]

src/devices/src/virtio/net/device.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ pub enum VirtioNetBackend {
6565
UnixstreamPath(PathBuf),
6666
UnixgramFd(RawFd),
6767
UnixgramPath(PathBuf, bool),
68+
#[cfg(target_os = "linux")]
69+
Tap(String),
6870
}
6971

7072
pub struct Net {

src/devices/src/virtio/net/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub const TX_INDEX: usize = 1;
1515

1616
mod backend;
1717
pub mod device;
18+
#[cfg(target_os = "linux")]
19+
mod tap;
1820
mod unixgram;
1921
mod unixstream;
2022
mod worker;

src/devices/src/virtio/net/tap.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use libc::{
2+
c_char, c_int, ifreq, IFF_NO_PI, IFF_TAP, IFF_VNET_HDR, O_RDWR, TUN_F_CSUM, TUN_F_TSO4,
3+
TUN_F_TSO6,
4+
};
5+
use nix::fcntl::{fcntl, FcntlArg, OFlag};
6+
use nix::unistd::{read, write};
7+
use nix::{ioctl_write_int, ioctl_write_ptr};
8+
use std::os::fd::{AsRawFd, RawFd};
9+
use std::{io, mem, ptr};
10+
11+
use super::backend::{ConnectError, NetBackend, ReadError, WriteError};
12+
13+
ioctl_write_ptr!(tunsetiff, b'T', 202, c_int);
14+
ioctl_write_int!(tunsetoffload, b'T', 208);
15+
ioctl_write_ptr!(tunsetvnethdrsz, b'T', 216, c_int);
16+
17+
pub struct Tap {
18+
fd: RawFd,
19+
}
20+
21+
impl Tap {
22+
/// Create an endpoint using the file descriptor of a tap device
23+
pub fn new(tap_name: String) -> Result<Self, ConnectError> {
24+
let fd = unsafe { libc::open(c"/dev/net/tun".as_ptr() as *const _, O_RDWR) };
25+
26+
if fd < 0 {
27+
return Err(ConnectError::OpenNetTun(io::Error::from_raw_os_error(fd)));
28+
}
29+
30+
let mut req: ifreq = unsafe { mem::zeroed() };
31+
32+
unsafe {
33+
ptr::copy_nonoverlapping(
34+
tap_name.as_ptr() as *const c_char,
35+
req.ifr_name.as_mut_ptr(),
36+
tap_name.len(),
37+
);
38+
}
39+
40+
req.ifr_ifru.ifru_flags = IFF_TAP as i16 | IFF_NO_PI as i16 | IFF_VNET_HDR as i16;
41+
42+
unsafe {
43+
if let Err(err) = tunsetiff(fd, &mut req as *mut _ as *mut _) {
44+
return Err(ConnectError::TunSetIff(io::Error::from(err)));
45+
}
46+
47+
// TODO(slp): replace hardcoded vnet size with cons
48+
if let Err(err) = tunsetvnethdrsz(fd, &12) {
49+
return Err(ConnectError::TunSetVnetHdrSz(io::Error::from(err)));
50+
}
51+
52+
if let Err(err) = tunsetoffload(fd, (TUN_F_CSUM | TUN_F_TSO4 | TUN_F_TSO6) as u64) {
53+
return Err(ConnectError::TunSetOffload(io::Error::from(err)));
54+
}
55+
}
56+
57+
match fcntl(fd, FcntlArg::F_GETFL) {
58+
Ok(flags) => {
59+
if let Err(e) = fcntl(
60+
fd,
61+
FcntlArg::F_SETFL(OFlag::from_bits_truncate(flags) | OFlag::O_NONBLOCK),
62+
) {
63+
warn!("error switching to non-blocking: id={fd}, err={e}");
64+
}
65+
}
66+
Err(e) => error!("couldn't obtain fd flags id={fd}, err={e}"),
67+
};
68+
69+
Ok(Self { fd })
70+
}
71+
}
72+
73+
impl NetBackend for Tap {
74+
/// Try to read a frame from the tap devie. If no bytes are available reports
75+
/// ReadError::NothingRead.
76+
fn read_frame(&mut self, buf: &mut [u8]) -> Result<usize, ReadError> {
77+
let frame_length = match read(self.fd, buf) {
78+
Ok(f) => f,
79+
#[allow(unreachable_patterns)]
80+
Err(nix::Error::EAGAIN | nix::Error::EWOULDBLOCK) => {
81+
return Err(ReadError::NothingRead)
82+
}
83+
Err(e) => {
84+
return Err(ReadError::Internal(e));
85+
}
86+
};
87+
debug!("Read eth frame from tap: {frame_length} bytes");
88+
Ok(frame_length)
89+
}
90+
91+
/// Try to write a frame to the tap device.
92+
fn write_frame(&mut self, _hdr_len: usize, buf: &mut [u8]) -> Result<(), WriteError> {
93+
let ret = write(self.fd, buf).map_err(WriteError::Internal)?;
94+
debug!("Written frame size={}, written={}", buf.len(), ret);
95+
Ok(())
96+
}
97+
98+
fn has_unfinished_write(&self) -> bool {
99+
false
100+
}
101+
102+
fn try_finish_write(&mut self, _hdr_len: usize, _buf: &[u8]) -> Result<(), WriteError> {
103+
// The tap backend doesn't do partial writes.
104+
Ok(())
105+
}
106+
107+
fn raw_socket_fd(&self) -> RawFd {
108+
self.fd.as_raw_fd()
109+
}
110+
}

src/devices/src/virtio/net/worker.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#[cfg(target_os = "linux")]
2+
use crate::virtio::net::tap::Tap;
13
use crate::virtio::net::unixgram::Unixgram;
24
use crate::virtio::net::unixstream::Unixstream;
35
use crate::virtio::net::{MAX_BUFFER_SIZE, QUEUE_SIZE, RX_INDEX, TX_INDEX};
@@ -53,6 +55,10 @@ impl NetWorker {
5355
VirtioNetBackend::UnixgramPath(path, vfkit_magic) => {
5456
Box::new(Unixgram::open(path, vfkit_magic).unwrap()) as Box<dyn NetBackend + Send>
5557
}
58+
#[cfg(target_os = "linux")]
59+
VirtioNetBackend::Tap(tap_name) => {
60+
Box::new(Tap::new(tap_name).unwrap()) as Box<dyn NetBackend + Send>
61+
}
5662
};
5763

5864
Self {

src/libkrun/src/lib.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,52 @@ pub unsafe extern "C" fn krun_add_net_unixgram(
876876
KRUN_SUCCESS
877877
}
878878

879+
#[allow(clippy::missing_safety_doc)]
880+
#[no_mangle]
881+
#[cfg(all(target_os = "linux", feature = "net"))]
882+
pub unsafe extern "C" fn krun_add_net_tap(
883+
ctx_id: u32,
884+
c_tap_name: *const c_char,
885+
c_mac: *const u8,
886+
features: u32,
887+
flags: u32,
888+
) -> i32 {
889+
if cfg!(not(feature = "net")) {
890+
return -libc::ENOTSUP;
891+
}
892+
893+
let tap_name = match CStr::from_ptr(c_tap_name).to_str() {
894+
Ok(tap_name) => tap_name.to_string(),
895+
Err(e) => {
896+
debug!("Error parsing tap_name: {e:?}");
897+
return -libc::EINVAL;
898+
}
899+
};
900+
901+
let mac: [u8; 6] = match slice::from_raw_parts(c_mac, 6).try_into() {
902+
Ok(m) => m,
903+
Err(_) => return -libc::EINVAL,
904+
};
905+
906+
if (features & !NET_ALL_FEATURES) != 0 {
907+
return -libc::EINVAL;
908+
}
909+
910+
/* The tap backend doesn't support any flags */
911+
if flags != 0 {
912+
return -libc::EINVAL;
913+
}
914+
915+
match CTX_MAP.lock().unwrap().entry(ctx_id) {
916+
Entry::Occupied(mut ctx_cfg) => {
917+
let cfg = ctx_cfg.get_mut();
918+
create_virtio_net(cfg, VirtioNetBackend::Tap(tap_name), mac, flags);
919+
}
920+
Entry::Vacant(_) => return -libc::ENOENT,
921+
}
922+
KRUN_SUCCESS
923+
}
924+
879925
#[allow(clippy::missing_safety_doc)]
880926
#[no_mangle]
881927
#[cfg(feature = "net")]

0 commit comments

Comments
 (0)