Compare commits

...

3 commits

5 changed files with 178 additions and 34 deletions

View file

@ -4,7 +4,10 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
log = "0.4.19" log = { version = "0.4.19", features = [
"max_level_debug",
"release_max_level_warn",
] }
uefi = { version = "0.24.0", features = ["alloc"] } uefi = { version = "0.24.0", features = ["alloc"] }
uefi-raw = "0.3.0" uefi-raw = "0.3.0"
uefi-services = "0.21.0" uefi-services = "0.21.0"

View file

@ -3,6 +3,16 @@
build: build:
cargo build --target x86_64-unknown-uefi cargo build --target x86_64-unknown-uefi
build-prod:
cargo build -r --target x86_64-unknown-uefi
restore:
cp build/backup/* build/
backup:
cp build/archlinux.qcow2 build/backup/
cp build/archlinux-1.qcow2 build/backup/
testvm: testvm:
cp target/x86_64-unknown-uefi/debug/http_boot_client.efi build/esp/efi/httpboot/httpboot.efi cp target/x86_64-unknown-uefi/debug/http_boot_client.efi build/esp/efi/httpboot/httpboot.efi
qemu-system-x86_64 -enable-kvm \ qemu-system-x86_64 -enable-kvm \
@ -10,3 +20,28 @@ testvm:
-drive if=pflash,format=raw,readonly=off,file=build/OVMF_VARS.fd \ -drive if=pflash,format=raw,readonly=off,file=build/OVMF_VARS.fd \
-drive format=raw,file=fat:rw:build/esp \ -drive format=raw,file=fat:rw:build/esp \
-netdev user,id=n1 -device virtio-net-pci,netdev=n1 -netdev user,id=n1 -device virtio-net-pci,netdev=n1
prodvm:
# ip tuntap add tap0 mode tap || true
# ip addr add 2001:abcd:1234:1::3/96 dev tap0 || true
# ip addr add 192.168.122.2/24 dev tap0 || true
# ip link set tap0 master virbr0 || true
# ip link set tap0 up || true
# ip link set virbr0 up || true
# sysctl net.ipv4.ip_forward=1
# iptables -t nat -A POSTROUTING -o enp6s0 -j MASQUERADE
cp target/x86_64-unknown-uefi/release/http_boot_client.efi build/esp/efi/httpboot/httpboot.efi
qemu-system-x86_64 -enable-kvm -m 1G -M q35 \
-drive if=pflash,format=raw,readonly=off,file=build/OVMF_CODE.fd \
-drive if=pflash,format=raw,readonly=off,file=build/OVMF_VARS.fd \
-drive file=build/archlinux.qcow2,format=qcow2,if=ide \
-drive file=build/archlinux-1.qcow2,format=qcow2,if=ide \
-nic bridge,br=virbr0,model=virtio-net-pci
# -netdev tap,id=mynet0,ifname=tap0,script=no,downscript=no \
# -device e1000,netdev=mynet0,mac=52:55:00:d1:55:01
push-prod:
scp target/x86_64-unknown-uefi/release/http_boot_client.efi root@192.168.122.2:/root/httpboot.efi

View file

@ -15,6 +15,7 @@ const HTTP_BOOT_VENDOR: VariableVendor =
pub struct Config { pub struct Config {
pub multicast_group: IpAddress, pub multicast_group: IpAddress,
pub multicast_port: u16, pub multicast_port: u16,
pub source_ip: IpAddress,
pub client_uuid: String, pub client_uuid: String,
} }
@ -37,6 +38,10 @@ pub fn read_config(runtime_services: &RuntimeServices) -> Result<Config> {
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x01,
]), ]),
source_ip: IpAddress::new_v6([
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01,
]),
multicast_port: 42, multicast_port: 42,
client_uuid: String::from("24609104-7ec6-423f-b57f-632b99b7566a"), client_uuid: String::from("24609104-7ec6-423f-b57f-632b99b7566a"),
}; };
@ -59,6 +64,11 @@ pub fn read_config(runtime_services: &RuntimeServices) -> Result<Config> {
conf.multicast_group = IpAddress::new_v6(group_buf); conf.multicast_group = IpAddress::new_v6(group_buf);
debug!("Got group {:?}", conf.multicast_group); debug!("Got group {:?}", conf.multicast_group);
let mut ip_buf: [u8; 16] = [0; 16]; // at most ipv6 is 16 bytes
get_var(runtime_services, cstr16!("HTTP_BOOT_IP"), &mut ip_buf)?;
conf.source_ip = IpAddress::new_v6(ip_buf);
debug!("Got ip address {:?}", conf.source_ip);
let mut uid_buf: [u8; 36] = [0; 36]; // uuid is 36 bytes let mut uid_buf: [u8; 36] = [0; 36]; // uuid is 36 bytes
get_var(runtime_services, cstr16!("HTTP_BOOT_UUID"), &mut uid_buf)?; get_var(runtime_services, cstr16!("HTTP_BOOT_UUID"), &mut uid_buf)?;
conf.client_uuid = match String::from_utf8(uid_buf.to_vec()) { conf.client_uuid = match String::from_utf8(uid_buf.to_vec()) {

View file

@ -4,23 +4,54 @@
pub mod config; pub mod config;
pub mod multicast; pub mod multicast;
use log::info; use alloc::boxed::Box;
use log::{debug, info};
use uefi::fs::PathBuf; use uefi::fs::PathBuf;
use uefi::prelude::*; use uefi::prelude::*;
use uefi::proto::debug;
use uefi::proto::device_path::{DevicePath, LoadedImageDevicePath};
use uefi::table::boot::LoadImageSource; use uefi::table::boot::LoadImageSource;
use uefi::{Error, Result, Status}; use uefi::{Error, Result, Status};
fn load_efi_app_from_path(boot_services: &BootServices, app_path: PathBuf) -> Result { use uefi::proto::device_path::text::{AllowShortcuts, DevicePathToText, DisplayOnly};
let mut fs_handler = boot_services.get_image_file_system(boot_services.image_handle())?; use uefi::proto::loaded_image::LoadedImage;
let app_content = match fs_handler.read(app_path) { use uefi::table::boot::SearchType;
Err(_e) => return Err(Error::from(Status::PROTOCOL_ERROR)), use uefi::Identify;
Ok(x) => x, extern crate alloc;
};
let image_source = LoadImageSource::FromBuffer { fn load_efi_app_from_path(boot_services: &BootServices, app_path: Box<DevicePath>) -> Result {
buffer: &app_content, debug!("checking value for image path");
file_path: None, let device_path_to_text_handle = *boot_services
.locate_handle_buffer(SearchType::ByProtocol(&DevicePathToText::GUID))?
.first()
.expect("DevicePathToText is missing");
let device_path_to_text =
boot_services.open_protocol_exclusive::<DevicePathToText>(device_path_to_text_handle)?;
let scoped_app_path = &*app_path;
let image_device_path_text = device_path_to_text
.convert_device_path_to_text(
boot_services,
scoped_app_path,
DisplayOnly(true),
AllowShortcuts(false),
)
.expect("convert_device_path_to_text failed");
info!("Image path: {}", &*image_device_path_text);
debug!("Loading image source");
let image_source = LoadImageSource::FromDevicePath {
device_path: scoped_app_path,
from_boot_manager: false,
}; };
debug!("Getting image handler");
let img_handler = boot_services.load_image(boot_services.image_handle(), image_source)?; let img_handler = boot_services.load_image(boot_services.image_handle(), image_source)?;
debug!("Starting new image");
boot_services.start_image(img_handler) boot_services.start_image(img_handler)
} }
@ -36,6 +67,27 @@ fn main(_handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
info!("init runtime services"); info!("init runtime services");
let runtime_services = system_table.runtime_services(); let runtime_services = system_table.runtime_services();
let loaded_image = boot_services
.open_protocol_exclusive::<LoadedImageDevicePath>(boot_services.image_handle())
.unwrap();
let device_path_to_text_handle = *boot_services
.locate_handle_buffer(SearchType::ByProtocol(&DevicePathToText::GUID))
.unwrap()
.first()
.expect("DevicePathToText is missing");
let device_path_to_text = boot_services
.open_protocol_exclusive::<DevicePathToText>(device_path_to_text_handle)
.unwrap();
let image_device_path_text = loaded_image
.to_string(boot_services, DisplayOnly(false), AllowShortcuts(false))
.expect("File path is not set")
.expect("File path is not set 2");
info!("Image path: {}", image_device_path_text);
let conf = config::read_config(runtime_services).unwrap(); let conf = config::read_config(runtime_services).unwrap();
let app_path = multicast::send_mcast(boot_services, &conf).unwrap(); let app_path = multicast::send_mcast(boot_services, &conf).unwrap();
load_efi_app_from_path(boot_services, app_path).unwrap(); load_efi_app_from_path(boot_services, app_path).unwrap();

View file

@ -1,14 +1,17 @@
use alloc::boxed::Box;
use alloc::string::String; use alloc::string::String;
use log::{debug, error, info}; use log::{debug, error, info};
use uefi::prelude::*; use uefi::prelude::*;
use uefi::proto::network::pxe::{BaseCode, UdpOpFlags}; use uefi::proto::network::pxe::{BaseCode, UdpOpFlags};
use uefi::proto::network::IpAddress;
use uefi::table::boot::{ScopedProtocol, SearchType}; use uefi::table::boot::{ScopedProtocol, SearchType};
use uefi::{Error, Identify, Result}; use uefi::{Error, Identify, Result};
extern crate alloc; extern crate alloc;
use crate::config::Config; use crate::config::Config;
use uefi::data_types::CStr16; use uefi::data_types::CStr16;
use uefi::fs::{Path, PathBuf}; use uefi::fs::{Path, PathBuf};
use uefi::proto::device_path::text::DevicePathFromText;
use uefi::proto::device_path::DevicePath;
use uefi::proto::network::IpAddress;
fn split_arg(raw_val: &str) -> Option<(&str, &str)> { fn split_arg(raw_val: &str) -> Option<(&str, &str)> {
let mut splitted = raw_val.split("="); let mut splitted = raw_val.split("=");
@ -23,9 +26,35 @@ fn split_arg(raw_val: &str) -> Option<(&str, &str)> {
return Some((arg_name, arg_value)); return Some((arg_name, arg_value));
} }
fn get_boot_path<'a>(msg: String) -> Result<PathBuf, &'a str> { fn get_boot_path<'a>(
debug!("Splitting message"); boot_services: &BootServices,
msg: String,
) -> Result<Box<DevicePath>, &'a str> {
debug!("Initializing DevicePathFromTextProtocol");
let device_path_from_text_handle = match boot_services
.locate_handle_buffer(SearchType::ByProtocol(&DevicePathFromText::GUID))
{
Err(e) => {
return Err(Error::new(e.status(), "failed to locate handle buffer"));
}
Ok(h) => match h.first() {
None => return Err(Error::new(Status::UNSUPPORTED, "no handle buffer")),
Some(h) => *h,
},
};
let device_path_from_text = match boot_services
.open_protocol_exclusive::<DevicePathFromText>(device_path_from_text_handle)
{
Err(e) => {
return Err(Error::new(
e.status(),
"failed to open device_path_from_text protocol",
))
}
Ok(d) => d,
};
debug!("Splitting message");
// a message has the following format: "ACTION arg1=val1,arg2=val2" // a message has the following format: "ACTION arg1=val1,arg2=val2"
let mut splitted_msg_iter = msg.split_ascii_whitespace(); let mut splitted_msg_iter = msg.split_ascii_whitespace();
@ -44,20 +73,21 @@ fn get_boot_path<'a>(msg: String) -> Result<PathBuf, &'a str> {
None => return Err(Error::new(Status::UNSUPPORTED, "empty list of arguments")), None => return Err(Error::new(Status::UNSUPPORTED, "empty list of arguments")),
Some(args_list) => { Some(args_list) => {
// split all the arguments in a comma separated list // split all the arguments in a comma separated list
for arg in args_list.split(",") { for arg in args_list.split(";") {
match split_arg(arg) { match split_arg(arg) {
None => debug!("unexpected format for argument {}", arg), None => debug!("unexpected format for argument {}", arg),
Some((arg_name, arg_value)) => match arg_name { Some((arg_name, arg_value)) => match arg_name {
"id" => debug!("found id {}", arg_value), "id" => debug!("found id {}", arg_value),
"efi_app" => { "efi_app" => {
debug!("found efi app {}", arg_value); debug!("found efi app {}", arg_value);
let mut buf = [0; 128]; let mut buf = [0; 2000];
let path_raw = CStr16::from_str_with_buf(arg_value, &mut buf) let path_raw = CStr16::from_str_with_buf(arg_value, &mut buf)
.expect("invalid str format"); .expect("invalid str format");
let p = Path::new(path_raw);
let mut res = PathBuf::new(); return Ok(device_path_from_text
res.push(p); .convert_text_to_device_path(path_raw)
return Ok(res); .expect("failed to parse device path")
.to_boxed());
} }
_ => debug!("unexpected argument {} with value {}", arg_name, arg_value), _ => debug!("unexpected argument {} with value {}", arg_name, arg_value),
}, },
@ -68,8 +98,11 @@ fn get_boot_path<'a>(msg: String) -> Result<PathBuf, &'a str> {
Err(Error::new(Status::UNSUPPORTED, "no efi app defined")) Err(Error::new(Status::UNSUPPORTED, "no efi app defined"))
} }
fn read_mcast_response<'a>(base_client: &mut ScopedProtocol<'_, BaseCode>) -> Result<PathBuf> { fn read_mcast_response<'a>(
let mut buf: [u8; 200] = [0; 200]; boot_services: &BootServices,
base_client: &mut ScopedProtocol<'_, BaseCode>,
) -> Result<Box<DevicePath>> {
let mut buf: [u8; 10000] = [0; 10000];
let n = base_client let n = base_client
.udp_read( .udp_read(
@ -89,7 +122,7 @@ fn read_mcast_response<'a>(base_client: &mut ScopedProtocol<'_, BaseCode>) -> Re
info!("Read {} bytes", n); info!("Read {} bytes", n);
let received_msg = String::from_utf8(buf[..n].to_vec()).unwrap(); let received_msg = String::from_utf8(buf[..n].to_vec()).unwrap();
info!("Received message {}", received_msg); info!("Received message {}", received_msg);
match get_boot_path(received_msg) { match get_boot_path(boot_services, received_msg) {
Ok(r) => Ok(r), Ok(r) => Ok(r),
Err(e) => { Err(e) => {
error!("failed to get boot path: {}", e.data()); error!("failed to get boot path: {}", e.data());
@ -98,21 +131,14 @@ fn read_mcast_response<'a>(base_client: &mut ScopedProtocol<'_, BaseCode>) -> Re
} }
} }
pub fn send_mcast<'a>(boot_service: &BootServices, conf: &Config) -> Result<PathBuf> { pub fn send_mcast<'a>(boot_services: &BootServices, conf: &Config) -> Result<Box<DevicePath>> {
debug!("Initializing base bootservice client handle"); debug!("Initializing base bootservice client handle");
let base_client_handle = *boot_service let base_client_handle = *boot_services
.locate_handle_buffer(SearchType::ByProtocol(&BaseCode::GUID))? .locate_handle_buffer(SearchType::ByProtocol(&BaseCode::GUID))?
.first() .first()
.expect("DevicePathToText is missing"); .expect("DevicePathToText is missing");
let mut base_client = boot_service.open_protocol_exclusive::<BaseCode>(base_client_handle)?; let mut base_client = boot_services.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="); let mut req_msg = String::from("BOOT_REQUEST id=");
req_msg.push_str(&conf.client_uuid); req_msg.push_str(&conf.client_uuid);
@ -121,8 +147,24 @@ pub fn send_mcast<'a>(boot_service: &BootServices, conf: &Config) -> Result<Path
debug!("Starting PXE client"); debug!("Starting PXE client");
base_client.start(true)?; base_client.start(true)?;
// debug!("Starting DHCP client");
// base_client.dhcp(false)?;
debug!("Setting IP address for PXE client"); debug!("Setting IP address for PXE client");
let mask = IpAddress::new_v6([
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
]);
// 2001:abcd:1234:1::2/96
let src_bytes: [u8; 16] = [
0x20, 0x01, 0xab, 0xcd, 0x12, 0x34, 0x00, 0x01, 0x00, 0x0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02,
];
let src_address: IpAddress = IpAddress::new_v6(src_bytes);
// base_client
base_client.set_station_ip(Some(&src_address), None)?; base_client.set_station_ip(Some(&src_address), None)?;
// base_client.set_station_ip(Some(&conf.source_ip), None)?;
info!("Sending UDP multicast message"); info!("Sending UDP multicast message");
base_client.udp_write( base_client.udp_write(
@ -130,12 +172,14 @@ pub fn send_mcast<'a>(boot_service: &BootServices, conf: &Config) -> Result<Path
&conf.multicast_group, &conf.multicast_group,
conf.multicast_port, conf.multicast_port,
None, None,
// Some(&conf.source_ip),
Some(&src_address), Some(&src_address),
// None,
None, None,
None, None,
req_msg.as_bytes(), req_msg.as_bytes(),
)?; )?;
let res = read_mcast_response(&mut base_client); let res = read_mcast_response(boot_services, &mut base_client);
info!("Stopping PXE client"); info!("Stopping PXE client");
base_client.stop()?; base_client.stop()?;