From 5bf4135847954bec6b8c90ef2996439783ae8056 Mon Sep 17 00:00:00 2001 From: diogo464 Date: Thu, 25 Sep 2025 17:32:25 +0100 Subject: booting debian --- .gitignore | 1 + Cargo.lock | 7 + Cargo.toml | 6 + src/main.rs | 756 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tftp/ipxe.efi | Bin 0 -> 1044480 bytes tftp/test.ipxe | 25 ++ 6 files changed, 795 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 tftp/ipxe.efi create mode 100644 tftp/test.ipxe diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5a09a45 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "netiso" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b0abcfb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "netiso" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..87ea283 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,756 @@ +use std::io::{BufRead, Cursor, Read, Result, Write}; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket}; + +const FLAG_BROADCAST: u16 = 1 << 15; + +const OPTION_CODE_PAD: u8 = 0; +const OPTION_CODE_END: u8 = 255; +const OPTION_CODE_VENDOR_CLASS_IDENTIFIER: u8 = 60; +const OPTION_CODE_USER_CLASS_INFORMATION: u8 = 77; + +const MAGIC_COOKIE: [u8; 4] = [0x63, 0x82, 0x53, 0x63]; + +//const BOOT_FILE_NAME: &[u8] = b"pxelinux.0"; +//const BOOT_FILE_NAME: &[u8] = b"debian-installer/amd64/bootnetx64.efi"; +const BOOT_FILE_NAME: &[u8] = b"ipxe.efi"; +const BOOT_FILE_NAME_IPXE: &[u8] = b"test.ipxe"; + +const LOCAL_IPV4: Ipv4Addr = Ipv4Addr::new(192, 168, 2, 184); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum BootOp { + Request, + Reply, +} + +impl BootOp { + pub const OP_REQUEST: u8 = 1; + pub const OP_REPLY: u8 = 2; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum HardwareType { + Ethernet, +} + +impl HardwareType { + pub const TYPE_ETHER: u8 = 1; + pub const LEN_ETHER: u8 = 6; +} + +#[derive(Debug, Clone)] +enum DhcpOption { + Pad, + End, + VendorClassIdentifier(String), + UserClassInformation(String), + Unknown { code: u8, data: Vec }, +} + +#[derive(Debug)] +struct DhcpPacket { + op: BootOp, + htype: HardwareType, + hlen: u8, + hops: u8, // should be zero + xid: u32, + secs: u16, + flags: u16, + ciaddr: Ipv4Addr, + yiaddr: Ipv4Addr, + siaddr: Ipv4Addr, + giaddr: Ipv4Addr, + chaddr: [u8; 16], + sname: [u8; 64], + file: [u8; 128], + options: Vec, +} + +fn read_u8(cursor: &mut Cursor<&[u8]>) -> Result { + let mut buf = [0u8; 1]; + cursor.read_exact(&mut buf)?; + Ok(buf[0]) +} + +fn read_u16(cursor: &mut Cursor<&[u8]>) -> Result { + let mut buf = [0u8; 2]; + cursor.read_exact(&mut buf)?; + Ok(u16::from_be_bytes(buf)) +} + +fn read_u32(cursor: &mut Cursor<&[u8]>) -> Result { + let mut buf = [0u8; 4]; + cursor.read_exact(&mut buf)?; + Ok(u32::from_be_bytes(buf)) +} + +fn read_arr(cursor: &mut Cursor<&[u8]>) -> Result<[u8; N]> { + let mut buf = [0u8; N]; + cursor.read_exact(&mut buf)?; + Ok(buf) +} + +fn read_null_terminated_vec(cursor: &mut Cursor<&[u8]>) -> Result> { + let mut buf = Vec::default(); + cursor.read_until(0, &mut buf)?; + buf.pop(); + Ok(buf) +} + +fn read_null_terminated_string(cursor: &mut Cursor<&[u8]>) -> Result { + let buf = read_null_terminated_vec(cursor)?; + Ok(String::from_utf8(buf).unwrap()) +} + +fn read_len8_prefixed_vec(cursor: &mut Cursor<&[u8]>) -> Result> { + let len = read_u8(cursor)?; + let mut buf = vec![0u8; len as usize]; + cursor.read_exact(&mut buf)?; + Ok(buf) +} + +fn read_len8_prefixed_string(cursor: &mut Cursor<&[u8]>) -> Result { + let buf = read_len8_prefixed_vec(cursor)?; + Ok(String::from_utf8(buf).unwrap()) +} + +fn read_ipv4(cursor: &mut Cursor<&[u8]>) -> Result { + Ok(Ipv4Addr::from_octets(read_arr(cursor)?)) +} + +fn read_op(cursor: &mut Cursor<&[u8]>) -> Result { + let v = read_u8(cursor)?; + match v { + BootOp::OP_REQUEST => Ok(BootOp::Request), + BootOp::OP_REPLY => Ok(BootOp::Reply), + _ => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "invalid boot op", + )), + } +} + +fn read_htype(cursor: &mut Cursor<&[u8]>) -> Result { + match read_u8(cursor)? { + HardwareType::TYPE_ETHER => Ok(HardwareType::Ethernet), + _ => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "invalid hardware type", + )), + } +} + +fn read_option(cursor: &mut Cursor<&[u8]>) -> Result { + let code = read_u8(cursor)?; + Ok(match code { + OPTION_CODE_PAD => DhcpOption::Pad, + OPTION_CODE_END => DhcpOption::End, + OPTION_CODE_VENDOR_CLASS_IDENTIFIER => { + DhcpOption::VendorClassIdentifier(read_len8_prefixed_string(cursor)?) + } + OPTION_CODE_USER_CLASS_INFORMATION => { + DhcpOption::UserClassInformation(read_len8_prefixed_string(cursor)?) + } + _ => { + let len = read_u8(cursor)?; + let mut data = vec![0u8; usize::from(len)]; + cursor.read_exact(&mut data)?; + DhcpOption::Unknown { code, data } + } + }) +} + +fn parse_packet(buf: &[u8]) -> Result { + let mut cursor = Cursor::new(buf); + let mut packet = DhcpPacket { + op: read_op(&mut cursor)?, + htype: read_htype(&mut cursor)?, + hlen: read_u8(&mut cursor)?, + hops: read_u8(&mut cursor)?, + xid: read_u32(&mut cursor)?, + secs: read_u16(&mut cursor)?, + flags: read_u16(&mut cursor)?, + ciaddr: read_ipv4(&mut cursor)?, + yiaddr: read_ipv4(&mut cursor)?, + siaddr: read_ipv4(&mut cursor)?, + giaddr: read_ipv4(&mut cursor)?, + chaddr: read_arr(&mut cursor)?, + sname: read_arr(&mut cursor)?, + file: read_arr(&mut cursor)?, + options: Default::default(), + }; + + let magic = read_arr::<4>(&mut cursor)?; + assert_eq!(magic, MAGIC_COOKIE); + + while cursor.position() < buf.len() as u64 { + let option = read_option(&mut cursor)?; + packet.options.push(option); + } + + Ok(packet) +} + +fn write_buf(writer: &mut Vec, buf: &[u8]) -> Result<()> { + writer.write_all(buf) +} + +fn write_u8(writer: &mut Vec, v: u8) -> Result<()> { + write_buf(writer, &[v]) +} + +fn write_u16(writer: &mut Vec, v: u16) -> Result<()> { + let buf = u16::to_be_bytes(v); + write_buf(writer, &buf) +} + +fn write_u32(writer: &mut Vec, v: u32) -> Result<()> { + let buf = u32::to_be_bytes(v); + write_buf(writer, &buf) +} + +fn write_ipv4(writer: &mut Vec, v: Ipv4Addr) -> Result<()> { + write_buf(writer, &v.octets()) +} + +fn write_boot_packet( + xid: u32, + chaddr: [u8; 16], + client_uuid: Option>, + ipxe: bool, +) -> Result> { + let mut writer = Vec::default(); + write_u8(&mut writer, BootOp::OP_REPLY)?; + write_u8(&mut writer, HardwareType::TYPE_ETHER)?; + write_u8(&mut writer, 6)?; + write_u8(&mut writer, 0)?; + write_u32(&mut writer, xid)?; + write_u16(&mut writer, 0)?; + write_u16(&mut writer, FLAG_BROADCAST)?; + write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // ciaddr + write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // yiaddr + write_ipv4(&mut writer, LOCAL_IPV4)?; // siaddr (TFTP server) + write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // giaddr + write_buf(&mut writer, &chaddr)?; + write_buf(&mut writer, &[0u8; 64])?; + write_buf(&mut writer, &[0u8; 128])?; + write_buf(&mut writer, &MAGIC_COOKIE)?; + + // Option 53: DHCP Message Type (DHCPOFFER) + write_u8(&mut writer, 53)?; + write_u8(&mut writer, 1)?; + write_u8(&mut writer, 2)?; // DHCPOFFER + + // Option 54: DHCP Server Identifier + write_u8(&mut writer, 54)?; + write_u8(&mut writer, 4)?; + write_ipv4(&mut writer, LOCAL_IPV4)?; // Your server IP + + // Option 60: Vendor Class Identifier + const PXE_CLIENT: &[u8] = b"PXEClient"; + write_u8(&mut writer, 60)?; + write_u8(&mut writer, PXE_CLIENT.len() as u8)?; + write_buf(&mut writer, PXE_CLIENT)?; + + // Option 97: Client Machine Identifier (UUID from client) + if let Some(uuid) = client_uuid { + write_u8(&mut writer, 97)?; + write_u8(&mut writer, uuid.len() as u8)?; + write_buf(&mut writer, &uuid)?; + } + + // TFTP server name + const SERVER_NAME: &[u8] = b"diogos-air"; + write_u8(&mut writer, 66)?; + write_u8(&mut writer, SERVER_NAME.len() as u8)?; + write_buf(&mut writer, SERVER_NAME)?; + + write_u8(&mut writer, 67)?; + if !ipxe { + write_u8(&mut writer, BOOT_FILE_NAME.len() as u8)?; + write_buf(&mut writer, BOOT_FILE_NAME)?; + } else { + write_u8(&mut writer, BOOT_FILE_NAME_IPXE.len() as u8)?; + write_buf(&mut writer, BOOT_FILE_NAME_IPXE)?; + } + + // Option 255: End + write_u8(&mut writer, 255)?; + + Ok(writer) +} + +fn write_boot_ack(xid: u32, chaddr: [u8; 16], client_uuid: Option>) -> Result> { + let mut writer = Vec::default(); + write_u8(&mut writer, BootOp::OP_REPLY)?; + write_u8(&mut writer, HardwareType::TYPE_ETHER)?; + write_u8(&mut writer, 6)?; + write_u8(&mut writer, 0)?; + write_u32(&mut writer, xid)?; + write_u16(&mut writer, 0)?; + write_u16(&mut writer, 0)?; + write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // ciaddr + write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // yiaddr + write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // siaddr (TFTP server) + write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // giaddr + write_buf(&mut writer, &chaddr)?; + write_buf(&mut writer, &[0u8; 64])?; + write_buf(&mut writer, &[0u8; 128])?; + write_buf(&mut writer, &MAGIC_COOKIE)?; + + // Option 53: DHCP Message Type (DHCPOFFER) + write_u8(&mut writer, 53)?; + write_u8(&mut writer, 1)?; + write_u8(&mut writer, 5)?; // DHCPACK + + // Option 54: DHCP Server Identifier + write_u8(&mut writer, 54)?; + write_u8(&mut writer, 4)?; + write_ipv4(&mut writer, LOCAL_IPV4)?; // Your server IP + + // Option 60: Vendor Class Identifier + const PXE_CLIENT: &[u8] = b"PXEClient"; + write_u8(&mut writer, 60)?; + write_u8(&mut writer, PXE_CLIENT.len() as u8)?; + write_buf(&mut writer, PXE_CLIENT)?; + + // Option 97: Client Machine Identifier (UUID from client) + if let Some(uuid) = client_uuid { + write_u8(&mut writer, 97)?; + write_u8(&mut writer, uuid.len() as u8)?; + write_buf(&mut writer, &uuid)?; + } + + // TFTP server name + const SERVER_NAME: &[u8] = b"diogos-air"; + write_u8(&mut writer, 66)?; + write_u8(&mut writer, SERVER_NAME.len() as u8)?; + write_buf(&mut writer, SERVER_NAME)?; + + // TFTP file name + write_u8(&mut writer, 67)?; + write_u8(&mut writer, BOOT_FILE_NAME.len() as u8)?; + write_buf(&mut writer, BOOT_FILE_NAME)?; + + write_u8(&mut writer, 71)?; + write_u8(&mut writer, 4)?; + write_buf(&mut writer, &[0, 0, 0, 0])?; + + // Option 255: End + write_u8(&mut writer, 255)?; + + Ok(writer) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct InvalidTftpOp(u16); + +impl std::fmt::Display for InvalidTftpOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "invalid tftp opcode '{}'", self.0) + } +} + +impl std::error::Error for InvalidTftpOp {} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum TftpOp { + ReadRequest, + WriteRequest, + Data, + Ack, + Error, + Oack, +} + +impl Into for TftpOp { + fn into(self) -> u16 { + match self { + TftpOp::ReadRequest => 1, + TftpOp::WriteRequest => 2, + TftpOp::Data => 3, + TftpOp::Ack => 4, + TftpOp::Error => 5, + TftpOp::Oack => 6, + } + } +} + +impl TryFrom for TftpOp { + type Error = InvalidTftpOp; + + fn try_from(value: u16) -> std::result::Result { + match value { + 1 => Ok(Self::ReadRequest), + 2 => Ok(Self::WriteRequest), + 3 => Ok(Self::Data), + 4 => Ok(Self::Ack), + 5 => Ok(Self::Error), + 6 => Ok(Self::Oack), + unknown => Err(InvalidTftpOp(unknown)), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum TftpMode { + NetAscii, + Octet, + Mail, +} + +#[derive(Debug)] +struct TftpRequestPacket { + filename: String, + mode: String, + tsize: Option, + blksize: Option, +} + +#[derive(Debug)] +struct TftpDataPacket { + block: u16, + data: Vec, +} + +#[derive(Debug)] +struct TftpAckPacket { + block: u16, +} + +#[derive(Debug)] +struct TftpErrorPacket { + code: u16, + message: String, +} + +fn tftp_request_packet_parse(cursor: &mut Cursor<&[u8]>) -> Result { + let filename = read_null_terminated_string(cursor)?; + let mode = read_null_terminated_string(cursor)?; + let mut tsize = None; + let mut blksize = None; + while let Ok(opt_name) = read_null_terminated_string(cursor) { + if opt_name.is_empty() { + break; + } + let opt_data = read_null_terminated_string(cursor)?; + match opt_name.as_str() { + "tsize" => tsize = Some(opt_data.parse::().unwrap()), + "blksize" => blksize = Some(opt_data.parse::().unwrap()), + _ => eprintln!("unknown tftp request option '{opt_name}'"), + } + } + + Ok(TftpRequestPacket { + filename, + mode, + tsize, + blksize, + }) +} + +fn tftp_data_packet_write(writer: &mut Vec, block: u16, data: Vec) -> Result<()> { + write_u16(writer, TftpOp::Data.into())?; + write_u16(writer, block)?; + write_buf(writer, &data)?; + Ok(()) +} + +fn tftp_oack_packet_write( + writer: &mut Vec, + tsize: Option, + blksize: Option, +) -> Result<()> { + write_u16(writer, TftpOp::Oack.into())?; + + // Only include options that were requested by the client + if let Some(blksize_val) = blksize { + write_buf(writer, b"blksize")?; + write_u8(writer, 0)?; // null terminator + let blksize_str = blksize_val.to_string(); + write_buf(writer, blksize_str.as_bytes())?; + write_u8(writer, 0)?; // null terminator + } + + if let Some(tsize_val) = tsize { + write_buf(writer, b"tsize")?; + write_u8(writer, 0)?; // null terminator + let tsize_str = tsize_val.to_string(); + write_buf(writer, tsize_str.as_bytes())?; + write_u8(writer, 0)?; // null terminator + } + + Ok(()) +} + +#[derive(Debug)] +enum TftpPacket { + Request(TftpRequestPacket), + Data(TftpDataPacket), + Ack(TftpAckPacket), + Error(TftpErrorPacket), +} + +fn tftp_packet_parse(cursor: &mut Cursor<&[u8]>) -> Result { + let op = TftpOp::try_from(read_u16(cursor)?).unwrap(); + match op { + TftpOp::ReadRequest | TftpOp::WriteRequest => { + let filename = read_null_terminated_string(cursor)?; + let mode = read_null_terminated_string(cursor)?; + let mut tsize = None; + let mut blksize = None; + + while let Ok(opt_name) = read_null_terminated_string(cursor) { + if opt_name.is_empty() { + break; + } + let opt_data = read_null_terminated_string(cursor)?; + match opt_name.as_str() { + "tsize" => tsize = Some(opt_data.parse::().unwrap()), + "blksize" => blksize = Some(opt_data.parse::().unwrap()), + _ => eprintln!("unknown tftp request option '{opt_name}'"), + } + } + + Ok(TftpPacket::Request(TftpRequestPacket { + filename, + mode, + tsize, + blksize, + })) + } + TftpOp::Data => { + let block = read_u16(cursor)?; + let mut data = Vec::new(); + cursor.read_to_end(&mut data)?; + Ok(TftpPacket::Data(TftpDataPacket { block, data })) + } + TftpOp::Ack => { + let block = read_u16(cursor)?; + Ok(TftpPacket::Ack(TftpAckPacket { block })) + } + TftpOp::Error => { + let code = read_u16(cursor)?; + let message = read_null_terminated_string(cursor)?; + Ok(TftpPacket::Error(TftpErrorPacket { code, message })) + } + TftpOp::Oack => { + // OACK parsing not implemented for now + Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "OACK parsing not implemented", + )) + } + } +} + +fn main() { + let socket67 = UdpSocket::bind("0.0.0.0:67").unwrap(); + socket67.set_broadcast(true).unwrap(); + socket67.set_nonblocking(true).unwrap(); + + let socket69 = UdpSocket::bind("0.0.0.0:69").unwrap(); + socket69.set_broadcast(false).unwrap(); + socket69.set_nonblocking(true).unwrap(); + + let socket4011 = UdpSocket::bind("0.0.0.0:4011").unwrap(); + socket4011.set_broadcast(true).unwrap(); + socket4011.set_nonblocking(true).unwrap(); + + let mut last_blksize = 512u64; + let mut current_file = String::new(); + + loop { + let mut buf = [0u8; 1500]; + + // Try port 67 first + if let Ok((n, addr)) = socket67.recv_from(&mut buf) { + println!("Received {} bytes from {} on port 67", n, addr); + handle_packet(&buf[..n], &socket67); + } else if let Ok((n, addr)) = socket4011.recv_from(&mut buf) { + println!("Received {} bytes from {} on port 4011", n, addr); + handle_packet_4011(&buf[..n], &socket4011, addr); + } else if let Ok((n, addr)) = socket69.recv_from(&mut buf) { + let mut cursor = Cursor::new(&buf[..n]); + + let packet = tftp_packet_parse(&mut cursor).unwrap(); + println!("Received TFTP request from {addr}: {packet:#?}"); + + let mut response = Vec::default(); + match packet { + TftpPacket::Request(tftp_request_packet) => { + println!( + "Request options: tsize={:?}, blksize={:?}", + tftp_request_packet.tsize, tftp_request_packet.blksize + ); + + let filepath = format!("tftp/{}", tftp_request_packet.filename); + current_file = filepath.clone(); + let meta = std::fs::metadata(&filepath).unwrap(); + let actual_file_size = meta.len(); + + // Only send OACK if client sent options + if tftp_request_packet.tsize.is_some() || tftp_request_packet.blksize.is_some() + { + if let Some(blksize) = tftp_request_packet.blksize { + last_blksize = blksize; + } + + let tsize_response = if tftp_request_packet.tsize.is_some() { + Some(actual_file_size) + } else { + None + }; + + tftp_oack_packet_write( + &mut response, + tsize_response, + tftp_request_packet.blksize, + ) + .unwrap(); + } else { + // No options, send first data block directly + let contents = std::fs::read(&filepath).unwrap(); + let block_size = 512; + let first_block = if contents.len() > block_size { + contents[..block_size].to_vec() + } else { + contents + }; + + tftp_data_packet_write(&mut response, 1, first_block).unwrap(); + } + } + TftpPacket::Data(tftp_data_packet) => { + println!("Received DATA packet: block {}", tftp_data_packet.block); + } + TftpPacket::Ack(tftp_ack_packet) => { + println!("Received ACK packet: block {}", tftp_ack_packet.block); + + let contents = std::fs::read(¤t_file).unwrap(); + let next_block = tftp_ack_packet.block + 1; + let start_offset = (next_block - 1) as u64 * last_blksize; + let end_offset = next_block as u64 * last_blksize; + let prev_start_offset = (next_block.saturating_sub(2)) as u64 * last_blksize; + let prev_remain = contents.len() - prev_start_offset as usize; + if prev_remain as u64 >= last_blksize || tftp_ack_packet.block == 0 { + let end = std::cmp::min(end_offset as usize, contents.len()); + let block_data = contents[start_offset as usize..end].to_vec(); + println!("sending tftp data packet with {} bytes", block_data.len()); + tftp_data_packet_write(&mut response, next_block, block_data).unwrap(); + } + } + TftpPacket::Error(tftp_error_packet) => { + println!( + "Received ERROR packet: code {}, message: {}", + tftp_error_packet.code, tftp_error_packet.message + ); + } + } + // + // let filepath = format!("tftp/{}", request.filename); + // let meta = std::fs::metadata(&filepath).unwrap(); + // let contents = std::fs::read(&filepath).unwrap(); + // let mut response = Vec::default(); + if !response.is_empty() { + socket69.send_to(&response, addr).unwrap(); + } + } else { + std::thread::sleep(std::time::Duration::from_millis(10)); + } + } +} + +fn handle_packet(buf: &[u8], socket: &UdpSocket) { + match parse_packet(buf) { + Ok(packet) => { + println!("Parsed DHCP packet: XID={:08x}", packet.xid); + + // Check if it's a PXE client and extract client UUID + let mut is_pxe = false; + let mut client_uuid = None; + let mut is_ipxe = false; + + for option in &packet.options { + match option { + DhcpOption::VendorClassIdentifier(vendor_class) => { + println!("Vendor class: {}", vendor_class); + if vendor_class.contains("PXEClient") { + is_pxe = true; + } + } + DhcpOption::UserClassInformation(user_class) => { + println!("User class: {}", user_class); + is_ipxe = true; + } + DhcpOption::Unknown { code: 97, data } => { + println!("Found client machine identifier"); + client_uuid = Some(data.clone()); + } + _ => {} + } + } + + if is_pxe { + println!("Responding to PXE client with DHCPOFFER"); + let response = + write_boot_packet(packet.xid, packet.chaddr, client_uuid, is_ipxe).unwrap(); + socket + .send_to(&response, SocketAddrV4::new(Ipv4Addr::BROADCAST, 68)) + .unwrap(); + } else { + println!("Not a PXE client, ignoring"); + } + } + Err(e) => { + println!("Failed to parse packet: {}", e); + } + } +} + +fn handle_packet_4011(buf: &[u8], socket: &UdpSocket, sender_addr: SocketAddr) { + match parse_packet(buf) { + Ok(packet) => { + println!("Parsed DHCP packet on 4011: XID={:08x}", packet.xid); + + // Extract client UUID + let mut client_uuid = None; + for option in &packet.options { + if let DhcpOption::Unknown { code: 97, data } = option { + client_uuid = Some(data.clone()); + break; + } + } + + println!("Responding with DHCPACK"); + let response = write_boot_ack(packet.xid, packet.chaddr, client_uuid).unwrap(); + socket.send_to(&response, sender_addr).unwrap(); + } + Err(e) => { + println!("Failed to parse packet on 4011: {}", e); + } + } +} + +const DHCP_PACKET_PAYLOAD: &'static [u8] = &[ + 0x1, 0x1, 0x6, 0x0, 0xf1, 0x25, 0x7c, 0x21, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b, 0x67, 0x3f, 0xda, 0x70, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x63, 0x82, 0x53, 0x63, 0x35, 0x1, 0x1, 0x39, + 0x2, 0x5, 0xc0, 0x37, 0x23, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0xc, 0xd, 0xf, 0x11, 0x12, 0x16, + 0x17, 0x1c, 0x28, 0x29, 0x2a, 0x2b, 0x32, 0x33, 0x36, 0x3a, 0x3b, 0x3c, 0x42, 0x43, 0x61, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x61, 0x11, 0x0, 0xcc, 0xfc, 0x32, 0x1b, 0xce, 0x2a, + 0xb2, 0x11, 0xa8, 0x5c, 0xb1, 0xac, 0x38, 0x38, 0x10, 0xf, 0x5e, 0x3, 0x1, 0x3, 0x10, 0x5d, + 0x2, 0x0, 0x7, 0x3c, 0x20, 0x50, 0x58, 0x45, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3a, 0x41, + 0x72, 0x63, 0x68, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x37, 0x3a, 0x55, 0x4e, 0x44, 0x49, 0x3a, 0x30, + 0x30, 0x33, 0x30, 0x31, 0x36, 0xff, +]; diff --git a/tftp/ipxe.efi b/tftp/ipxe.efi new file mode 100644 index 0000000..d1c12b3 Binary files /dev/null and b/tftp/ipxe.efi differ diff --git a/tftp/test.ipxe b/tftp/test.ipxe new file mode 100644 index 0000000..f4ee267 --- /dev/null +++ b/tftp/test.ipxe @@ -0,0 +1,25 @@ +#!ipxe + +:start +#console --picture http://boot.ipxe.org/ipxe.png +menu debian +item --gap -- ---------------------- Net installer ----------------------------- +item --key 3 Debian9_x86 Debian 9 (3)2-bit net install +item --key 6 Debian9_x86_64 Debian 9 (6)4-bit net install +item --gap -- ------------------------- Options -------------------------------- +item --key g goback (G)o back to previous menu +choose version && goto ${version} || goto start + +:Debian9_x86 +echo Booting Debian 9 32-bit +kernel http://deb.debian.org/debian/dists/stretch/main/installer-i386/current/images/netboot/debian-installer/i386/linux initrd=initrd.gz +initrd http://deb.debian.org/debian/dists/stretch/main/installer-i386/current/images/netboot/debian-installer/i386/initrd.gz +boot || imgfree +goto start + +:Debian9_x86_64 +echo Booting Debian 9 64-bit +kernel http://deb.debian.org/debian/dists/trixie/main/installer-amd64/current/images/netboot/debian-installer/amd64/linux initrd=initrd.gz +initrd http://deb.debian.org/debian/dists/trixie/main/installer-amd64/current/images/netboot/debian-installer/amd64/initrd.gz +boot || imgfree +goto start -- cgit