Skip to content

Commit d6e7e17

Browse files
hoshinolinaslp
authored andcommitted
Support native X11 forwarding
If the host has DISPLAY set, parse it and forward that UNIX socket to the guest via vsock, then forward :1 on the guest to this vsock, and finally set HOST_DISPLAY=:1 on the guest. Apps that want to directly talk to X11 on the host (instead of going through sommelier) should set DISPLAY=$HOST_DISPLAY and LIBGL_ALWAYS_SOFTWARE=true. This setup allows X11 apps in the host and the guest to cooperate (e.g. XEmbed) but requires software rendering inside the guest. TODO: Figure out why things hang without LIBGL_ALWAYS_SOFTWARE=true. Ideally DRI3 should fail and fall back to software automatically. Signed-off-by: Asahi Lina <[email protected]>
1 parent 6996cdd commit d6e7e17

8 files changed

Lines changed: 137 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ license = "MIT"
1515
anyhow = { version = "1.0.82", default-features = false }
1616
bindgen = { version = "0.69.4", default-features = false }
1717
bpaf = { version = "0.9.11", default-features = false }
18+
byteorder = { version = "1.5.0", default-features = false }
1819
env_logger = { version = "0.11.3", default-features = false }
1920
krun-sys = { path = "crates/krun-sys", default-features = false }
2021
log = { version = "0.4.21", default-features = false }

crates/krun-guest/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ license = { workspace = true }
1111
[dependencies]
1212
anyhow = { workspace = true, features = ["std"] }
1313
bpaf = { workspace = true, features = [] }
14+
byteorder = { workspace = true, features = ["std"] }
1415
env_logger = { workspace = true, features = ["auto-color", "humantime", "unstable-kv"] }
1516
log = { workspace = true, features = ["kv"] }
1617
nix = { workspace = true, features = ["user"] }

crates/krun-guest/src/bin/krun-guest.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use krun_guest::net::configure_network;
1010
use krun_guest::socket::setup_socket_proxy;
1111
use krun_guest::sommelier::exec_sommelier;
1212
use krun_guest::user::setup_user;
13+
use krun_guest::x11::setup_x11_forwarding;
1314
use log::debug;
1415
use rustix::process::{getrlimit, setrlimit, Resource};
1516

@@ -52,6 +53,8 @@ fn main() -> Result<()> {
5253
let pulse_path = pulse_path.join("native");
5354
setup_socket_proxy(pulse_path, 3333)?;
5455

56+
setup_x11_forwarding(run_path)?;
57+
5558
// Will not return if successful.
5659
exec_sommelier(&options.command, &options.command_args)
5760
.context("Failed to execute sommelier")?;

crates/krun-guest/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ pub mod net;
55
pub mod socket;
66
pub mod sommelier;
77
pub mod user;
8+
pub mod x11;

crates/krun-guest/src/x11.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use crate::socket::setup_socket_proxy;
2+
3+
use anyhow::{anyhow, Context, Result};
4+
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
5+
use std::env;
6+
use std::fs::File;
7+
use std::io::{Read, Write};
8+
use std::path::Path;
9+
10+
pub fn setup_x11_forwarding<P>(run_path: P) -> Result<()>
11+
where
12+
P: AsRef<Path>,
13+
{
14+
// Set by krun if DISPLAY was provided from the host.
15+
let host_display = match env::var("HOST_DISPLAY") {
16+
Ok(d) => d,
17+
Err(_) => return Ok(()),
18+
};
19+
20+
if !host_display.starts_with(":") {
21+
return Err(anyhow!("Invalid host DISPLAY"));
22+
}
23+
let host_display = &host_display[1..];
24+
25+
setup_socket_proxy(Path::new("/tmp/.X11-unix/X1"), 6000)?;
26+
27+
// Set HOST_DISPLAY to :1, which is the display number within the guest
28+
// at which the actual host display is accessible.
29+
// SAFETY: Safe if and only if `krun-guest` program is not multithreaded.
30+
// See https://doc.rust-lang.org/std/env/fn.set_var.html#safety
31+
env::set_var("HOST_DISPLAY", ":1");
32+
33+
if let Ok(xauthority) = std::env::var("XAUTHORITY") {
34+
let src_path = format!("/run/krun-host/{}", xauthority);
35+
let mut rdr = File::open(src_path)?;
36+
37+
let dst_path = run_path.as_ref().join("xauth");
38+
let mut wtr = File::options()
39+
.write(true)
40+
.create(true)
41+
.truncate(true)
42+
.open(&dst_path)
43+
.context("Failed to create `xauth`")?;
44+
45+
while let Ok(family) = rdr.read_u16::<BigEndian>() {
46+
let mut addr = vec![0u8; rdr.read_u16::<BigEndian>()? as usize];
47+
rdr.read_exact(&mut addr)?;
48+
49+
let mut display = vec![0u8; rdr.read_u16::<BigEndian>()? as usize];
50+
rdr.read_exact(&mut display)?;
51+
52+
let mut name = vec![0u8; rdr.read_u16::<BigEndian>()? as usize];
53+
rdr.read_exact(&mut name)?;
54+
55+
let mut data = vec![0u8; rdr.read_u16::<BigEndian>()? as usize];
56+
rdr.read_exact(&mut data)?;
57+
58+
// Only copy the wildcard entry
59+
if family != 0xffff {
60+
continue;
61+
}
62+
63+
// Check for the correct host display
64+
if display != host_display.as_bytes() {
65+
continue;
66+
}
67+
68+
// Always use display number 1
69+
let display = b"1";
70+
71+
wtr.write_u16::<BigEndian>(family)?;
72+
wtr.write_u16::<BigEndian>(addr.len().try_into()?)?;
73+
wtr.write(&addr)?;
74+
wtr.write_u16::<BigEndian>(display.len().try_into()?)?;
75+
wtr.write(display)?;
76+
wtr.write_u16::<BigEndian>(name.len().try_into()?)?;
77+
wtr.write(&name)?;
78+
wtr.write_u16::<BigEndian>(data.len().try_into()?)?;
79+
wtr.write(&data)?;
80+
81+
break;
82+
}
83+
84+
// SAFETY: Safe if and only if `krun-guest` program is not multithreaded.
85+
// See https://doc.rust-lang.org/std/env/fn.set_var.html#safety
86+
env::set_var("XAUTHORITY", dst_path);
87+
}
88+
89+
Ok(())
90+
}

crates/krun/src/bin/krun.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,27 @@ fn main() -> Result<()> {
181181
}
182182
}
183183

184+
// Forward the native X11 display into the guest as a socket
185+
if let Ok(x11_display) = env::var("DISPLAY") {
186+
if x11_display.starts_with(":") {
187+
let socket_path = Path::new("/tmp/.X11-unix/").join(format!("X{}", &x11_display[1..]));
188+
if socket_path.exists() {
189+
let socket_path = CString::new(
190+
socket_path
191+
.to_str()
192+
.expect("socket_path should not contain invalid UTF-8"),
193+
)
194+
.context("Failed to process dynamic socket path as it contains NUL character")?;
195+
// SAFETY: `socket_path` is a pointer to a `CString` with long enough lifetime.
196+
let err = unsafe { krun_add_vsock_port(ctx_id, 6000, socket_path.as_ptr()) };
197+
if err < 0 {
198+
let err = Errno::from_raw_os_error(-err);
199+
return Err(err).context("Failed to configure vsock for host X11 socket");
200+
}
201+
}
202+
}
203+
}
204+
184205
let username = env::var("USER").context("Failed to get username from environment")?;
185206
let user = User::from_name(&username)
186207
.map_err(Into::into)

crates/krun/src/env.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,19 @@ pub fn prepare_env_vars(env: Vec<(String, Option<String>)>) -> Result<HashMap<St
6565
env_map.insert(key, value);
6666
}
6767

68+
// If we have an X11 display in the host, set HOST_DISPLAY in the guest.
69+
// krun-guest will then use this to set up xauth and replace it with :1
70+
// (which is forwarded to the host display).
71+
if let Ok(display) = env::var("DISPLAY") {
72+
env_map.insert("HOST_DISPLAY".to_string(), display);
73+
74+
// And forward XAUTHORITY. This will be modified to fix the
75+
// display name in krun-guest.
76+
if let Ok(xauthority) = env::var("XAUTHORITY") {
77+
env_map.insert("XAUTHORITY".to_string(), xauthority);
78+
}
79+
}
80+
6881
debug!(env:? = env_map; "env vars");
6982

7083
Ok(env_map)

0 commit comments

Comments
 (0)