add first version of uefi client
This commit is contained in:
parent
a292bbda03
commit
64ea8e2659
8 changed files with 292 additions and 4 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -19,3 +19,4 @@ Cargo.lock
|
||||||
# Added by cargo
|
# Added by cargo
|
||||||
|
|
||||||
/target
|
/target
|
||||||
|
build/
|
|
@ -3,6 +3,8 @@ name = "http_boot_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
log = "0.4.19"
|
||||||
|
uefi = { version = "0.24.0", features = ["alloc"] }
|
||||||
|
uefi-raw = "0.3.0"
|
||||||
|
uefi-services = "0.21.0"
|
||||||
|
|
12
Makefile
Normal file
12
Makefile
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
.PHONY: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
cargo build --target x86_64-unknown-uefi
|
||||||
|
|
||||||
|
testvm:
|
||||||
|
cp target/x86_64-unknown-uefi/debug/http_boot_client.efi build/esp/efi/httpboot/httpboot.efi
|
||||||
|
qemu-system-x86_64 -enable-kvm \
|
||||||
|
-drive if=pflash,format=raw,readonly=on,file=build/OVMF_CODE.fd \
|
||||||
|
-drive if=pflash,format=raw,readonly=off,file=build/OVMF_VARS.fd \
|
||||||
|
-drive format=raw,file=fat:rw:build/esp \
|
||||||
|
-netdev user,id=n1 -device virtio-net-pci,netdev=n1
|
14
README.md
14
README.md
|
@ -1,2 +1,16 @@
|
||||||
# http-boot-client
|
# http-boot-client
|
||||||
|
|
||||||
|
Commands to add the correct values to UEFI shell
|
||||||
|
|
||||||
|
```
|
||||||
|
# 0x2a corresponds to 42
|
||||||
|
setvar -rt -nv -guid 638a24ce-a0ce-4908-8152-b2a4e8b2f29d HTTP_BOOT_MCAST_PORT =2a
|
||||||
|
setvar -rt -nv -guid 638a24ce-a0ce-4908-8152-b2a4e8b2f29d HTTP_BOOT_MCAST_GROUP =ff0200000000000000000000abcd1234
|
||||||
|
setvar -rt -nv -guid 638a24ce-a0ce-4908-8152-b2a4e8b2f29d HTTP_BOOT_UUID ="ffaf8813-fe27-4b1d-9d24-edb1baee9db9"
|
||||||
|
```
|
||||||
|
|
||||||
|
View current values
|
||||||
|
|
||||||
|
```
|
||||||
|
dmpstore -guid 638a24ce-a0ce-4908-8152-b2a4e8b2f29d
|
||||||
|
```
|
||||||
|
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
targets = ["aarch64-unknown-uefi", "i686-unknown-uefi", "x86_64-unknown-uefi"]
|
71
src/config.rs
Normal file
71
src/config.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
use alloc::string::String;
|
||||||
|
use log::{debug, info};
|
||||||
|
use uefi::prelude::*;
|
||||||
|
use uefi::proto::network::IpAddress;
|
||||||
|
use uefi::CStr16;
|
||||||
|
use uefi::{Error, Result};
|
||||||
|
use uefi_raw::table::runtime::VariableVendor;
|
||||||
|
use uefi_raw::{Guid, Status};
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
const HTTP_BOOT_VENDOR: VariableVendor =
|
||||||
|
VariableVendor(Guid::parse_or_panic("638a24ce-a0ce-4908-8152-b2a4e8b2f29d"));
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
pub multicast_group: IpAddress,
|
||||||
|
pub multicast_port: u16,
|
||||||
|
pub client_uuid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_var<'a>(
|
||||||
|
runtime_services: &RuntimeServices,
|
||||||
|
var_name: &'a CStr16,
|
||||||
|
res: &'a mut [u8],
|
||||||
|
) -> Result<()> {
|
||||||
|
match runtime_services.get_variable(var_name, &HTTP_BOOT_VENDOR, res) {
|
||||||
|
Err(e) => Err(e),
|
||||||
|
Ok(_r) => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_config(runtime_services: &RuntimeServices) -> Result<Config> {
|
||||||
|
info!("Reading EFI configuration from EFI vars");
|
||||||
|
|
||||||
|
let mut conf = Config {
|
||||||
|
multicast_group: IpAddress::new_v6([
|
||||||
|
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x01,
|
||||||
|
]),
|
||||||
|
multicast_port: 42,
|
||||||
|
client_uuid: String::from("24609104-7ec6-423f-b57f-632b99b7566a"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut port_buf: [u8; 2] = [0; 2]; // port is 16 bytes encoded
|
||||||
|
get_var(
|
||||||
|
runtime_services,
|
||||||
|
cstr16!("HTTP_BOOT_MCAST_PORT"),
|
||||||
|
&mut port_buf,
|
||||||
|
)?;
|
||||||
|
conf.multicast_port = u16::from_ne_bytes(port_buf);
|
||||||
|
debug!("Got port {}", conf.multicast_port);
|
||||||
|
|
||||||
|
let mut group_buf: [u8; 16] = [0; 16]; // at most ipv6 is 16 bytes
|
||||||
|
get_var(
|
||||||
|
runtime_services,
|
||||||
|
cstr16!("HTTP_BOOT_MCAST_GROUP"),
|
||||||
|
&mut group_buf,
|
||||||
|
)?;
|
||||||
|
conf.multicast_group = IpAddress::new_v6(group_buf);
|
||||||
|
debug!("Got group {:?}", conf.multicast_group);
|
||||||
|
|
||||||
|
let mut uid_buf: [u8; 36] = [0; 36]; // uuid is 36 bytes
|
||||||
|
get_var(runtime_services, cstr16!("HTTP_BOOT_UUID"), &mut uid_buf)?;
|
||||||
|
conf.client_uuid = match String::from_utf8(uid_buf.to_vec()) {
|
||||||
|
Err(_e) => return Err(Error::from(Status::INVALID_PARAMETER)),
|
||||||
|
Ok(s) => s,
|
||||||
|
};
|
||||||
|
debug!("Got uuid {:?}", conf.client_uuid);
|
||||||
|
|
||||||
|
Ok(conf)
|
||||||
|
}
|
45
src/main.rs
45
src/main.rs
|
@ -1,3 +1,44 @@
|
||||||
fn main() {
|
#![no_main]
|
||||||
println!("Hello, world!");
|
#![no_std]
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod multicast;
|
||||||
|
|
||||||
|
use log::info;
|
||||||
|
use uefi::fs::PathBuf;
|
||||||
|
use uefi::prelude::*;
|
||||||
|
use uefi::table::boot::LoadImageSource;
|
||||||
|
use uefi::{Error, Result, Status};
|
||||||
|
|
||||||
|
fn load_efi_app_from_path(boot_services: &BootServices, app_path: PathBuf) -> Result {
|
||||||
|
let mut fs_handler = boot_services.get_image_file_system(boot_services.image_handle())?;
|
||||||
|
let app_content = match fs_handler.read(app_path) {
|
||||||
|
Err(_e) => return Err(Error::from(Status::PROTOCOL_ERROR)),
|
||||||
|
Ok(x) => x,
|
||||||
|
};
|
||||||
|
let image_source = LoadImageSource::FromBuffer {
|
||||||
|
buffer: &app_content,
|
||||||
|
file_path: None,
|
||||||
|
};
|
||||||
|
let img_handler = boot_services.load_image(boot_services.image_handle(), image_source)?;
|
||||||
|
boot_services.start_image(img_handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[entry]
|
||||||
|
fn main(_handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
|
||||||
|
info!("init service");
|
||||||
|
|
||||||
|
uefi_services::init(&mut system_table).unwrap();
|
||||||
|
|
||||||
|
info!("init boot services");
|
||||||
|
let boot_services = system_table.boot_services();
|
||||||
|
|
||||||
|
info!("init runtime services");
|
||||||
|
let runtime_services = system_table.runtime_services();
|
||||||
|
|
||||||
|
let conf = config::read_config(runtime_services).unwrap();
|
||||||
|
let app_path = multicast::send_mcast(boot_services, &conf).unwrap();
|
||||||
|
load_efi_app_from_path(boot_services, app_path).unwrap();
|
||||||
|
|
||||||
|
Status::SUCCESS
|
||||||
}
|
}
|
||||||
|
|
145
src/multicast.rs
Normal file
145
src/multicast.rs
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
use alloc::string::String;
|
||||||
|
use log::{debug, error, info};
|
||||||
|
use uefi::prelude::*;
|
||||||
|
use uefi::proto::network::pxe::{BaseCode, UdpOpFlags};
|
||||||
|
use uefi::proto::network::IpAddress;
|
||||||
|
use uefi::table::boot::{ScopedProtocol, SearchType};
|
||||||
|
use uefi::{Error, Identify, Result};
|
||||||
|
extern crate alloc;
|
||||||
|
use crate::config::Config;
|
||||||
|
use uefi::data_types::CStr16;
|
||||||
|
use uefi::fs::{Path, PathBuf};
|
||||||
|
|
||||||
|
fn split_arg(raw_val: &str) -> Option<(&str, &str)> {
|
||||||
|
let mut splitted = raw_val.split("=");
|
||||||
|
let arg_name = match splitted.next() {
|
||||||
|
None => return None,
|
||||||
|
Some(v) => v,
|
||||||
|
};
|
||||||
|
let arg_value = match splitted.next() {
|
||||||
|
None => return None,
|
||||||
|
Some(v) => v,
|
||||||
|
};
|
||||||
|
return Some((arg_name, arg_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_boot_path<'a>(msg: String) -> Result<PathBuf, &'a str> {
|
||||||
|
debug!("Splitting message");
|
||||||
|
|
||||||
|
// a message has the following format: "ACTION arg1=val1,arg2=val2"
|
||||||
|
let mut splitted_msg_iter = msg.split_ascii_whitespace();
|
||||||
|
|
||||||
|
debug!("Checking if message is a valid BOOT_ACCEPT message");
|
||||||
|
match splitted_msg_iter.next() {
|
||||||
|
None => return Err(Error::new(Status::UNSUPPORTED, "empty message")),
|
||||||
|
Some(action) => match action {
|
||||||
|
"BOOT_ACCEPT" => (),
|
||||||
|
"BOOT_DENY" => return Err(Error::new(Status::UNSUPPORTED, "boot refused")),
|
||||||
|
_ => return Err(Error::new(Status::UNSUPPORTED, "unexpected action")),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Parsing message arguments");
|
||||||
|
match splitted_msg_iter.next() {
|
||||||
|
None => return Err(Error::new(Status::UNSUPPORTED, "empty list of arguments")),
|
||||||
|
Some(args_list) => {
|
||||||
|
// split all the arguments in a comma separated list
|
||||||
|
for arg in args_list.split(",") {
|
||||||
|
match split_arg(arg) {
|
||||||
|
None => debug!("unexpected format for argument {}", arg),
|
||||||
|
Some((arg_name, arg_value)) => match arg_name {
|
||||||
|
"id" => debug!("found id {}", arg_value),
|
||||||
|
"efi_app" => {
|
||||||
|
debug!("found efi app {}", arg_value);
|
||||||
|
let mut buf = [0; 128];
|
||||||
|
let path_raw = CStr16::from_str_with_buf(arg_value, &mut buf)
|
||||||
|
.expect("invalid str format");
|
||||||
|
let p = Path::new(path_raw);
|
||||||
|
let mut res = PathBuf::new();
|
||||||
|
res.push(p);
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
_ => debug!("unexpected argument {} with value {}", arg_name, arg_value),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::new(Status::UNSUPPORTED, "no efi app defined"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_mcast_response<'a>(base_client: &mut ScopedProtocol<'_, BaseCode>) -> Result<PathBuf> {
|
||||||
|
let mut buf: [u8; 200] = [0; 200];
|
||||||
|
|
||||||
|
let n = base_client
|
||||||
|
.udp_read(
|
||||||
|
UdpOpFlags::ANY_DEST_IP
|
||||||
|
| UdpOpFlags::ANY_DEST_PORT
|
||||||
|
| UdpOpFlags::ANY_SRC_IP
|
||||||
|
| UdpOpFlags::ANY_SRC_PORT,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
&mut buf,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
info!("Read {} bytes", n);
|
||||||
|
let received_msg = String::from_utf8(buf[..n].to_vec()).unwrap();
|
||||||
|
info!("Received message {}", received_msg);
|
||||||
|
match get_boot_path(received_msg) {
|
||||||
|
Ok(r) => Ok(r),
|
||||||
|
Err(e) => {
|
||||||
|
error!("failed to get boot path: {}", e.data());
|
||||||
|
Err(e.to_err_without_payload())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_mcast<'a>(boot_service: &BootServices, conf: &Config) -> Result<PathBuf> {
|
||||||
|
debug!("Initializing base bootservice client handle");
|
||||||
|
let base_client_handle = *boot_service
|
||||||
|
.locate_handle_buffer(SearchType::ByProtocol(&BaseCode::GUID))?
|
||||||
|
.first()
|
||||||
|
.expect("DevicePathToText is missing");
|
||||||
|
|
||||||
|
let mut base_client = boot_service.open_protocol_exclusive::<BaseCode>(base_client_handle)?;
|
||||||
|
|
||||||
|
debug!("Initializing source IP address");
|
||||||
|
let src_bytes: [u8; 16] = [
|
||||||
|
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x02, 0x16, 0x3e, 0xff, 0xfe, 0xc2, 0x16,
|
||||||
|
0xb7,
|
||||||
|
];
|
||||||
|
let src_address: IpAddress = IpAddress::new_v6(src_bytes);
|
||||||
|
|
||||||
|
let mut req_msg = String::from("BOOT_REQUEST id=");
|
||||||
|
req_msg.push_str(&conf.client_uuid);
|
||||||
|
|
||||||
|
info!("Starting PXE client");
|
||||||
|
debug!("Starting PXE client");
|
||||||
|
base_client.start(true)?;
|
||||||
|
|
||||||
|
debug!("Setting IP address for PXE client");
|
||||||
|
base_client.set_station_ip(Some(&src_address), None)?;
|
||||||
|
|
||||||
|
info!("Sending UDP multicast message");
|
||||||
|
base_client.udp_write(
|
||||||
|
UdpOpFlags::MAY_FRAGMENT,
|
||||||
|
&conf.multicast_group,
|
||||||
|
conf.multicast_port,
|
||||||
|
None,
|
||||||
|
Some(&src_address),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
req_msg.as_bytes(),
|
||||||
|
)?;
|
||||||
|
let res = read_mcast_response(&mut base_client);
|
||||||
|
|
||||||
|
info!("Stopping PXE client");
|
||||||
|
base_client.stop()?;
|
||||||
|
|
||||||
|
info!("PXE client successfully executed");
|
||||||
|
res
|
||||||
|
}
|
Loading…
Reference in a new issue