Skip to main content

Road to PXE - Day 1

·741 words·4 mins

why? #

PXE has always been something that seemed like magic to me, I turn on a computer, spam F12 and if the gods are gracious I get a nice lil screen to install an OS. I knew there were some DHCP options or something and there is a TFTP server for the files but past that I’m lost.

I’m a curious lil guy so instead of doing the normal thing and reading the wikipedia page then installing iVentoy I want to make my own PXE server.

I’ll be documenting the suffering I endure in these blog posts.

How does DHCP relate to PXE #

So after a quick google I found this sweet flowchart on the tianocore github

Tianocore PXE boot flowchart
Tianocore PXE boot flowchart

So it looks like DHCP Server tells the client where to go looking for the PXE Server and then the PXE Server sends down this magical thing called a bootfile which starts the process of booting the machine over the network.

The issue we have here is that depending on the architecture of the System and whether its BIOS or UEFI we need to send a different bootfile.

The bootfile dilemma #

Okay, so now that we know we have to provide a different bootfile depending on architecture that complicates things.

At the outset of this process my friend suggested I just install iVentoy like everyone else…. Fuck you Corey, in this household we PXE boot from artisanal rust servers like the hipster fucks we are.

So uh, how does iVentoy handle the bootfile dilemma… like this There are 2 primary ways iVentoy deals with DHCP

  1. iVentoy is DHCP - makes sense
  2. iVentoy intercepts DHCP - wut 0 _ 0

Something, Something, DHCP… #

$ cargo init rusted-pxe

Sick, I have a rust project, how do I do a DHCP… Looks like there are two options:

  1. dora - full dhcp server written in rust with a SQL server on the backend - overkill I don’t really want to replace my routers DHCP
  2. dhcproto - have to implement the server part myself but this does the hard packet stuff

Let’s go with option 2 I guess

What is DHCP? #

So now that we have decided to use dhcproto to parse DHCP packets, how do I get the packets? After a little googling I established the following knowledge…

  1. DHCP runs on ports 67, and 68
  2. DHCP is broadcast traffic so we can snoop it easy - no weird man in the middle shit
  3. DHCP is built on top of UDP

That all sounds great except the UDP part, I have never built something on top of UDP before.

Building something on top of UDP #

So basically I need to make a UDP server that listens to broadcast traffic on port 67. I already know I am probably going to be using some tokio libraries so preferably want to build it in that.

*GOOGLING INTENSIFIES*

After some… okay maybe a lot of googling I landed on this rough code

use std::io;
use tokio::net::UdpSocket;

struct DHCPServer {
    socket: UdpSocket,
    buf: Vec<u8>,
}

impl DHCPServer {
    async fn run(self) -> Result<(), io::Error> {
        let DHCPServer { socket, mut buf } = self;

        println!("DHCP Listening on: {}", socket.local_addr()?);
        loop {
            let valid_bytes = socket.recv(&mut buf).await?;
            let data = &buf[..valid_bytes];

            println!("{} bytes recieved", data.len())
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), io::Error> {
    let socket = UdpSocket::bind("0.0.0.0:67").await.unwrap();
    socket.set_broadcast(true).unwrap();

    let dhcp_server = DHCPServer {
        socket,
        buf: vec![0; 1500],
    };

    dhcp_server.run().await.unwrap();
    Ok(())
}

WHY THE FUCK ISNT IT WORKING #

This is where I start to lose my mind, my server is up and seems to be working but I just cannot for the life of me get it to work. I rewrite it without tokio… no joy… I boot up wireshark.. I can see the traffic. I boot up netcat… I cant see the traffic… huh I went around and around troubleshooting for ages until…

screenshot of discord message
fuck.jpg
yep… hours of my life wasted cause of security… how am I supposed to live laugh love under these conditions.

anyway… SUCCESS

a terminal window with a server recieving bytes
LETS FUCKING GOOOOO

Cool, we are getting bytes; now for some dhcproto magic.

println!("DHCP Listening on: {}", socket.local_addr()?);
loop {
    let valid_bytes = socket.recv(&mut buf).await?;
    let data = &buf[..valid_bytes];

    println!("{} bytes recieved", data.len());

    let msg = Message::decode(&mut Decoder::new(&data)).unwrap();
    println!("{:?}", msg)
}

a terminal window with a server recieving bytes
LETS FUCKING GOOOOO AGAIN

Anyhow it is far too late and I have had some success so I will go sleep 💤 Tune in again for more sleep derpived madness