diff options
| author | diogo464 <[email protected]> | 2025-10-08 19:03:56 +0100 |
|---|---|---|
| committer | diogo464 <[email protected]> | 2025-10-08 19:03:56 +0100 |
| commit | 89db26c86bf48a4c527778fc254765a38b7e9085 (patch) | |
| tree | c3b441f3db8b9e17b652269cb0bd5ac720d2e51a /src | |
| parent | 7a52879e0db0e4fb311ec840938c5fc4e5775afc (diff) | |
dhcp module split done
Diffstat (limited to 'src')
| -rw-r--r-- | src/dhcp.rs | 233 | ||||
| -rw-r--r-- | src/main.rs | 382 |
2 files changed, 253 insertions, 362 deletions
diff --git a/src/dhcp.rs b/src/dhcp.rs index b53ee92..38cc8e4 100644 --- a/src/dhcp.rs +++ b/src/dhcp.rs | |||
| @@ -1,11 +1,12 @@ | |||
| 1 | use std::{ | 1 | use std::{ |
| 2 | io::{Result, Write}, | 2 | io::{Cursor, Read as _, Result, Write}, |
| 3 | net::Ipv4Addr, | 3 | net::Ipv4Addr, |
| 4 | }; | 4 | }; |
| 5 | 5 | ||
| 6 | use crate::wire; | 6 | use crate::wire; |
| 7 | 7 | ||
| 8 | const MAGIC_COOKIE: [u8; 4] = [0x63, 0x82, 0x53, 0x63]; | 8 | const MAGIC_COOKIE: [u8; 4] = [0x63, 0x82, 0x53, 0x63]; |
| 9 | const FLAG_BROADCAST: u16 = 1 << 15; | ||
| 9 | 10 | ||
| 10 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] | 11 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] |
| 11 | pub enum BootOp { | 12 | pub enum BootOp { |
| @@ -55,14 +56,17 @@ impl From<HardwareType> for u8 { | |||
| 55 | 56 | ||
| 56 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | 57 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| 57 | pub enum DhcpMessageType { | 58 | pub enum DhcpMessageType { |
| 59 | Offer, | ||
| 58 | Ack, | 60 | Ack, |
| 59 | } | 61 | } |
| 60 | 62 | ||
| 61 | impl DhcpMessageType { | 63 | impl DhcpMessageType { |
| 64 | pub const CODE_OFFER: u8 = 2; | ||
| 62 | pub const CODE_ACK: u8 = 5; | 65 | pub const CODE_ACK: u8 = 5; |
| 63 | 66 | ||
| 64 | pub fn code(&self) -> u8 { | 67 | pub fn code(&self) -> u8 { |
| 65 | match self { | 68 | match self { |
| 69 | DhcpMessageType::Offer => Self::CODE_OFFER, | ||
| 66 | DhcpMessageType::Ack => Self::CODE_ACK, | 70 | DhcpMessageType::Ack => Self::CODE_ACK, |
| 67 | } | 71 | } |
| 68 | } | 72 | } |
| @@ -78,7 +82,7 @@ pub enum DhcpOption { | |||
| 78 | TftpServerName(String), | 82 | TftpServerName(String), |
| 79 | TftpFileName(String), | 83 | TftpFileName(String), |
| 80 | UserClassInformation(String), | 84 | UserClassInformation(String), |
| 81 | ClientMachineIdentifier(String), | 85 | ClientMachineIdentifier(Vec<u8>), |
| 82 | Unknown { code: u8, data: Vec<u8> }, | 86 | Unknown { code: u8, data: Vec<u8> }, |
| 83 | } | 87 | } |
| 84 | 88 | ||
| @@ -113,6 +117,7 @@ impl DhcpOption { | |||
| 113 | pub struct DhcpPacket { | 117 | pub struct DhcpPacket { |
| 114 | pub op: BootOp, | 118 | pub op: BootOp, |
| 115 | pub htype: HardwareType, | 119 | pub htype: HardwareType, |
| 120 | pub hops: u8, | ||
| 116 | pub xid: u32, | 121 | pub xid: u32, |
| 117 | pub secs: u16, | 122 | pub secs: u16, |
| 118 | pub flags: u16, | 123 | pub flags: u16, |
| @@ -122,9 +127,9 @@ pub struct DhcpPacket { | |||
| 122 | pub giaddr: Ipv4Addr, | 127 | pub giaddr: Ipv4Addr, |
| 123 | pub chaddr: [u8; 16], | 128 | pub chaddr: [u8; 16], |
| 124 | // server host name | 129 | // server host name |
| 125 | pub sname: Option<String>, | 130 | pub sname: String, |
| 126 | // boot file name | 131 | // boot file name |
| 127 | pub file: Option<String>, | 132 | pub file: String, |
| 128 | pub options: Vec<DhcpOption>, | 133 | pub options: Vec<DhcpOption>, |
| 129 | } | 134 | } |
| 130 | 135 | ||
| @@ -133,6 +138,7 @@ impl Default for DhcpPacket { | |||
| 133 | Self { | 138 | Self { |
| 134 | op: Default::default(), | 139 | op: Default::default(), |
| 135 | htype: Default::default(), | 140 | htype: Default::default(), |
| 141 | hops: Default::default(), | ||
| 136 | xid: Default::default(), | 142 | xid: Default::default(), |
| 137 | secs: Default::default(), | 143 | secs: Default::default(), |
| 138 | flags: Default::default(), | 144 | flags: Default::default(), |
| @@ -148,11 +154,209 @@ impl Default for DhcpPacket { | |||
| 148 | } | 154 | } |
| 149 | } | 155 | } |
| 150 | 156 | ||
| 157 | impl DhcpPacket { | ||
| 158 | pub fn new_boot( | ||
| 159 | xid: u32, | ||
| 160 | chaddr: [u8; 16], | ||
| 161 | client_uuid: Vec<u8>, | ||
| 162 | local_ip: Ipv4Addr, | ||
| 163 | local_hostname: String, | ||
| 164 | filename: String, | ||
| 165 | ) -> Self { | ||
| 166 | Self { | ||
| 167 | op: BootOp::Reply, | ||
| 168 | htype: HardwareType::Ethernet, | ||
| 169 | hops: Default::default(), | ||
| 170 | xid, | ||
| 171 | secs: Default::default(), | ||
| 172 | flags: FLAG_BROADCAST, | ||
| 173 | ciaddr: Ipv4Addr::UNSPECIFIED, | ||
| 174 | yiaddr: Ipv4Addr::UNSPECIFIED, | ||
| 175 | siaddr: local_ip, | ||
| 176 | giaddr: Ipv4Addr::UNSPECIFIED, | ||
| 177 | chaddr, | ||
| 178 | sname: Default::default(), | ||
| 179 | file: Default::default(), | ||
| 180 | options: vec![ | ||
| 181 | DhcpOption::MessageType(DhcpMessageType::Offer), | ||
| 182 | DhcpOption::ServerIdentifier(local_ip), | ||
| 183 | DhcpOption::VendorClassIdentifier("PXEClient".to_string()), | ||
| 184 | DhcpOption::ClientMachineIdentifier(client_uuid), | ||
| 185 | DhcpOption::TftpServerName(local_hostname), | ||
| 186 | DhcpOption::TftpFileName(filename), | ||
| 187 | ], | ||
| 188 | } | ||
| 189 | } | ||
| 190 | |||
| 191 | pub fn new_boot_ack( | ||
| 192 | xid: u32, | ||
| 193 | chaddr: [u8; 16], | ||
| 194 | client_uuid: Vec<u8>, | ||
| 195 | local_ip: Ipv4Addr, | ||
| 196 | hostname: String, | ||
| 197 | filename: String, | ||
| 198 | ) -> Self { | ||
| 199 | Self { | ||
| 200 | op: BootOp::Reply, | ||
| 201 | htype: HardwareType::Ethernet, | ||
| 202 | hops: 0, | ||
| 203 | xid, | ||
| 204 | secs: 0, | ||
| 205 | flags: 0, | ||
| 206 | ciaddr: Ipv4Addr::UNSPECIFIED, | ||
| 207 | yiaddr: Ipv4Addr::UNSPECIFIED, | ||
| 208 | siaddr: Ipv4Addr::UNSPECIFIED, | ||
| 209 | giaddr: Ipv4Addr::UNSPECIFIED, | ||
| 210 | chaddr, | ||
| 211 | sname: Default::default(), | ||
| 212 | file: Default::default(), | ||
| 213 | options: vec![ | ||
| 214 | DhcpOption::MessageType(DhcpMessageType::Ack), | ||
| 215 | DhcpOption::ServerIdentifier(local_ip), | ||
| 216 | DhcpOption::VendorClassIdentifier("PXEClient".to_string()), | ||
| 217 | DhcpOption::ClientMachineIdentifier(client_uuid), | ||
| 218 | DhcpOption::TftpServerName(hostname), | ||
| 219 | DhcpOption::TftpFileName(filename), | ||
| 220 | ], | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | pub fn write<W: Write>(&self, writer: W) -> Result<()> { | ||
| 225 | write_packet(writer, self) | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | fn read_u8(cursor: &mut Cursor<&[u8]>) -> Result<u8> { | ||
| 230 | let mut buf = [0u8; 1]; | ||
| 231 | cursor.read_exact(&mut buf)?; | ||
| 232 | Ok(buf[0]) | ||
| 233 | } | ||
| 234 | |||
| 235 | fn read_u16(cursor: &mut Cursor<&[u8]>) -> Result<u16> { | ||
| 236 | let mut buf = [0u8; 2]; | ||
| 237 | cursor.read_exact(&mut buf)?; | ||
| 238 | Ok(u16::from_be_bytes(buf)) | ||
| 239 | } | ||
| 240 | |||
| 241 | fn read_u32(cursor: &mut Cursor<&[u8]>) -> Result<u32> { | ||
| 242 | let mut buf = [0u8; 4]; | ||
| 243 | cursor.read_exact(&mut buf)?; | ||
| 244 | Ok(u32::from_be_bytes(buf)) | ||
| 245 | } | ||
| 246 | |||
| 247 | fn read_arr<const N: usize>(cursor: &mut Cursor<&[u8]>) -> Result<[u8; N]> { | ||
| 248 | let mut buf = [0u8; N]; | ||
| 249 | cursor.read_exact(&mut buf)?; | ||
| 250 | Ok(buf) | ||
| 251 | } | ||
| 252 | |||
| 253 | fn read_len8_prefixed_vec(cursor: &mut Cursor<&[u8]>) -> Result<Vec<u8>> { | ||
| 254 | let len = read_u8(cursor)?; | ||
| 255 | let mut buf = vec![0u8; len as usize]; | ||
| 256 | cursor.read_exact(&mut buf)?; | ||
| 257 | Ok(buf) | ||
| 258 | } | ||
| 259 | |||
| 260 | fn read_len8_prefixed_string(cursor: &mut Cursor<&[u8]>) -> Result<String> { | ||
| 261 | let buf = read_len8_prefixed_vec(cursor)?; | ||
| 262 | Ok(String::from_utf8(buf).unwrap()) | ||
| 263 | } | ||
| 264 | |||
| 265 | fn read_ipv4(cursor: &mut Cursor<&[u8]>) -> Result<Ipv4Addr> { | ||
| 266 | Ok(Ipv4Addr::from_octets(read_arr(cursor)?)) | ||
| 267 | } | ||
| 268 | |||
| 269 | fn read_op(cursor: &mut Cursor<&[u8]>) -> Result<BootOp> { | ||
| 270 | let v = read_u8(cursor)?; | ||
| 271 | match v { | ||
| 272 | BootOp::OP_REQUEST => Ok(BootOp::Request), | ||
| 273 | BootOp::OP_REPLY => Ok(BootOp::Reply), | ||
| 274 | _ => Err(std::io::Error::new( | ||
| 275 | std::io::ErrorKind::InvalidData, | ||
| 276 | "invalid boot op", | ||
| 277 | )), | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | fn read_htype(cursor: &mut Cursor<&[u8]>) -> Result<HardwareType> { | ||
| 282 | let ty = read_u8(cursor)?; | ||
| 283 | let len = read_u8(cursor)?; | ||
| 284 | match (ty, len) { | ||
| 285 | (HardwareType::TYPE_ETHER, HardwareType::LEN_ETHER) => Ok(HardwareType::Ethernet), | ||
| 286 | _ => Err(std::io::Error::new( | ||
| 287 | std::io::ErrorKind::InvalidData, | ||
| 288 | "invalid hardware type", | ||
| 289 | )), | ||
| 290 | } | ||
| 291 | } | ||
| 292 | |||
| 293 | fn read_option(cursor: &mut Cursor<&[u8]>) -> Result<DhcpOption> { | ||
| 294 | let code = read_u8(cursor)?; | ||
| 295 | Ok(match code { | ||
| 296 | DhcpOption::CODE_PAD => DhcpOption::Pad, | ||
| 297 | DhcpOption::CODE_END => DhcpOption::End, | ||
| 298 | DhcpOption::CODE_VENDOR_CLASS_IDENTIFIER => { | ||
| 299 | DhcpOption::VendorClassIdentifier(read_len8_prefixed_string(cursor)?) | ||
| 300 | } | ||
| 301 | DhcpOption::CODE_USER_CLASS_INFORMATION => { | ||
| 302 | DhcpOption::UserClassInformation(read_len8_prefixed_string(cursor)?) | ||
| 303 | } | ||
| 304 | _ => { | ||
| 305 | let len = read_u8(cursor)?; | ||
| 306 | let mut data = vec![0u8; usize::from(len)]; | ||
| 307 | cursor.read_exact(&mut data)?; | ||
| 308 | DhcpOption::Unknown { code, data } | ||
| 309 | } | ||
| 310 | }) | ||
| 311 | } | ||
| 312 | |||
| 313 | fn read_sname(cursor: &mut Cursor<&[u8]>) -> Result<String> { | ||
| 314 | let arr = read_arr::<64>(cursor)?; | ||
| 315 | let sname = std::str::from_utf8(&arr).unwrap(); | ||
| 316 | Ok(sname.to_string()) | ||
| 317 | } | ||
| 318 | |||
| 319 | fn read_filename(cursor: &mut Cursor<&[u8]>) -> Result<String> { | ||
| 320 | let arr = read_arr::<128>(cursor)?; | ||
| 321 | let filename = std::str::from_utf8(&arr).unwrap(); | ||
| 322 | Ok(filename.to_string()) | ||
| 323 | } | ||
| 324 | |||
| 325 | pub fn parse_packet(buf: &[u8]) -> Result<DhcpPacket> { | ||
| 326 | let mut cursor = Cursor::new(buf); | ||
| 327 | let mut packet = DhcpPacket { | ||
| 328 | op: read_op(&mut cursor)?, | ||
| 329 | htype: read_htype(&mut cursor)?, | ||
| 330 | hops: read_u8(&mut cursor)?, | ||
| 331 | xid: read_u32(&mut cursor)?, | ||
| 332 | secs: read_u16(&mut cursor)?, | ||
| 333 | flags: read_u16(&mut cursor)?, | ||
| 334 | ciaddr: read_ipv4(&mut cursor)?, | ||
| 335 | yiaddr: read_ipv4(&mut cursor)?, | ||
| 336 | siaddr: read_ipv4(&mut cursor)?, | ||
| 337 | giaddr: read_ipv4(&mut cursor)?, | ||
| 338 | chaddr: read_arr(&mut cursor)?, | ||
| 339 | sname: read_sname(&mut cursor)?, | ||
| 340 | file: read_filename(&mut cursor)?, | ||
| 341 | options: Default::default(), | ||
| 342 | }; | ||
| 343 | |||
| 344 | let magic = read_arr::<4>(&mut cursor)?; | ||
| 345 | assert_eq!(magic, MAGIC_COOKIE); | ||
| 346 | |||
| 347 | while cursor.position() < buf.len() as u64 { | ||
| 348 | let option = read_option(&mut cursor)?; | ||
| 349 | packet.options.push(option); | ||
| 350 | } | ||
| 351 | |||
| 352 | Ok(packet) | ||
| 353 | } | ||
| 354 | |||
| 151 | pub fn write_packet<W: Write>(mut writer: W, packet: &DhcpPacket) -> Result<()> { | 355 | pub fn write_packet<W: Write>(mut writer: W, packet: &DhcpPacket) -> Result<()> { |
| 152 | wire::write_u8(&mut writer, u8::from(packet.op))?; | 356 | wire::write_u8(&mut writer, u8::from(packet.op))?; |
| 153 | wire::write_u8(&mut writer, u8::from(packet.htype))?; | 357 | wire::write_u8(&mut writer, u8::from(packet.htype))?; |
| 154 | wire::write_u8(&mut writer, packet.htype.hardware_len())?; | 358 | wire::write_u8(&mut writer, packet.htype.hardware_len())?; |
| 155 | wire::write_u8(&mut writer, 0)?; // hops | 359 | wire::write_u8(&mut writer, packet.hops)?; |
| 156 | wire::write_u32(&mut writer, packet.xid)?; | 360 | wire::write_u32(&mut writer, packet.xid)?; |
| 157 | wire::write_u16(&mut writer, packet.secs)?; | 361 | wire::write_u16(&mut writer, packet.secs)?; |
| 158 | wire::write_u16(&mut writer, packet.flags)?; | 362 | wire::write_u16(&mut writer, packet.flags)?; |
| @@ -161,14 +365,10 @@ pub fn write_packet<W: Write>(mut writer: W, packet: &DhcpPacket) -> Result<()> | |||
| 161 | wire::write_ipv4(&mut writer, packet.siaddr)?; | 365 | wire::write_ipv4(&mut writer, packet.siaddr)?; |
| 162 | wire::write_ipv4(&mut writer, packet.giaddr)?; | 366 | wire::write_ipv4(&mut writer, packet.giaddr)?; |
| 163 | wire::write(&mut writer, &packet.chaddr)?; | 367 | wire::write(&mut writer, &packet.chaddr)?; |
| 164 | match &packet.sname { | 368 | //wire::write_null_terminated_string(&mut writer, &packet.sname)?; |
| 165 | Some(name) => wire::write_null_terminated_string(&mut writer, &name)?, | 369 | //wire::write_null_terminated_string(&mut writer, &packet.file)?; |
| 166 | None => wire::write_null_terminated_string(&mut writer, "")?, | 370 | wire::write(&mut writer, &vec![0u8; 64])?; |
| 167 | }; | 371 | wire::write(&mut writer, &vec![0u8; 128])?; |
| 168 | match &packet.file { | ||
| 169 | Some(name) => wire::write_null_terminated_string(&mut writer, &name)?, | ||
| 170 | None => wire::write_null_terminated_string(&mut writer, "")?, | ||
| 171 | }; | ||
| 172 | wire::write(&mut writer, &MAGIC_COOKIE)?; | 372 | wire::write(&mut writer, &MAGIC_COOKIE)?; |
| 173 | for option in &packet.options { | 373 | for option in &packet.options { |
| 174 | write_option(&mut writer, option)?; | 374 | write_option(&mut writer, option)?; |
| @@ -198,7 +398,7 @@ pub fn write_option<W: Write>(mut writer: W, option: &DhcpOption) -> Result<()> | |||
| 198 | write_option_len_prefixed_string(&mut writer, &user_class)? | 398 | write_option_len_prefixed_string(&mut writer, &user_class)? |
| 199 | } | 399 | } |
| 200 | DhcpOption::ClientMachineIdentifier(identifier) => { | 400 | DhcpOption::ClientMachineIdentifier(identifier) => { |
| 201 | write_option_len_prefixed_string(&mut writer, &identifier)? | 401 | write_option_len_prefixed_buf(&mut writer, &identifier)? |
| 202 | } | 402 | } |
| 203 | DhcpOption::Unknown { data, .. } => { | 403 | DhcpOption::Unknown { data, .. } => { |
| 204 | wire::write_u8(&mut writer, u8::try_from(data.len()).unwrap())?; | 404 | wire::write_u8(&mut writer, u8::try_from(data.len()).unwrap())?; |
| @@ -212,3 +412,8 @@ fn write_option_len_prefixed_string<W: Write>(mut writer: W, s: &str) -> Result< | |||
| 212 | wire::write_u8(&mut writer, u8::try_from(s.len()).unwrap())?; | 412 | wire::write_u8(&mut writer, u8::try_from(s.len()).unwrap())?; |
| 213 | wire::write(&mut writer, s.as_bytes()) | 413 | wire::write(&mut writer, s.as_bytes()) |
| 214 | } | 414 | } |
| 415 | |||
| 416 | fn write_option_len_prefixed_buf<W: Write>(mut writer: W, s: &[u8]) -> Result<()> { | ||
| 417 | wire::write_u8(&mut writer, u8::try_from(s.len()).unwrap())?; | ||
| 418 | wire::write(&mut writer, s) | ||
| 419 | } | ||
diff --git a/src/main.rs b/src/main.rs index 321b5ea..51bbd77 100644 --- a/src/main.rs +++ b/src/main.rs | |||
| @@ -3,351 +3,17 @@ pub mod dhcp; | |||
| 3 | pub mod tftp; | 3 | pub mod tftp; |
| 4 | pub mod wire; | 4 | pub mod wire; |
| 5 | 5 | ||
| 6 | use std::io::{BufRead, Cursor, Read, Result, Write}; | 6 | use std::{ |
| 7 | use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket}; | 7 | io::Result, |
| 8 | net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket}, | ||
| 9 | }; | ||
| 8 | 10 | ||
| 9 | use ipnet::Ipv4Net; | 11 | use ipnet::Ipv4Net; |
| 10 | 12 | ||
| 11 | const FLAG_BROADCAST: u16 = 1 << 15; | 13 | use crate::dhcp::{DhcpOption, DhcpPacket}; |
| 12 | 14 | ||
| 13 | const OPTION_CODE_PAD: u8 = 0; | 15 | const LOCAL_IPV4: Ipv4Addr = Ipv4Addr::new(192, 168, 1, 103); |
| 14 | const OPTION_CODE_END: u8 = 255; | 16 | const LOCAL_HOSTNAME: &'static str = "Diogos-Air"; |
| 15 | const OPTION_CODE_VENDOR_CLASS_IDENTIFIER: u8 = 60; | ||
| 16 | const OPTION_CODE_USER_CLASS_INFORMATION: u8 = 77; | ||
| 17 | |||
| 18 | const MAGIC_COOKIE: [u8; 4] = [0x63, 0x82, 0x53, 0x63]; | ||
| 19 | |||
| 20 | //const BOOT_FILE_NAME: &[u8] = b"pxelinux.0"; | ||
| 21 | //const BOOT_FILE_NAME: &[u8] = b"debian-installer/amd64/bootnetx64.efi"; | ||
| 22 | const BOOT_FILE_NAME: &[u8] = b"ipxe.efi"; | ||
| 23 | const BOOT_FILE_NAME_IPXE: &[u8] = b"test.ipxe"; | ||
| 24 | |||
| 25 | const LOCAL_IPV4: Ipv4Addr = Ipv4Addr::new(192, 168, 1, 100); | ||
| 26 | |||
| 27 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| 28 | enum BootOp { | ||
| 29 | Request, | ||
| 30 | Reply, | ||
| 31 | } | ||
| 32 | |||
| 33 | impl BootOp { | ||
| 34 | pub const OP_REQUEST: u8 = 1; | ||
| 35 | pub const OP_REPLY: u8 = 2; | ||
| 36 | } | ||
| 37 | |||
| 38 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| 39 | enum HardwareType { | ||
| 40 | Ethernet, | ||
| 41 | } | ||
| 42 | |||
| 43 | impl HardwareType { | ||
| 44 | pub const TYPE_ETHER: u8 = 1; | ||
| 45 | pub const LEN_ETHER: u8 = 6; | ||
| 46 | } | ||
| 47 | |||
| 48 | #[derive(Debug, Clone)] | ||
| 49 | enum DhcpOption { | ||
| 50 | Pad, | ||
| 51 | End, | ||
| 52 | VendorClassIdentifier(String), | ||
| 53 | UserClassInformation(String), | ||
| 54 | Unknown { code: u8, data: Vec<u8> }, | ||
| 55 | } | ||
| 56 | |||
| 57 | #[derive(Debug)] | ||
| 58 | struct DhcpPacket { | ||
| 59 | op: BootOp, | ||
| 60 | htype: HardwareType, | ||
| 61 | hlen: u8, | ||
| 62 | hops: u8, // should be zero | ||
| 63 | xid: u32, | ||
| 64 | secs: u16, | ||
| 65 | flags: u16, | ||
| 66 | ciaddr: Ipv4Addr, | ||
| 67 | yiaddr: Ipv4Addr, | ||
| 68 | siaddr: Ipv4Addr, | ||
| 69 | giaddr: Ipv4Addr, | ||
| 70 | chaddr: [u8; 16], | ||
| 71 | sname: [u8; 64], | ||
| 72 | file: [u8; 128], | ||
| 73 | options: Vec<DhcpOption>, | ||
| 74 | } | ||
| 75 | |||
| 76 | fn read_u8(cursor: &mut Cursor<&[u8]>) -> Result<u8> { | ||
| 77 | let mut buf = [0u8; 1]; | ||
| 78 | cursor.read_exact(&mut buf)?; | ||
| 79 | Ok(buf[0]) | ||
| 80 | } | ||
| 81 | |||
| 82 | fn read_u16(cursor: &mut Cursor<&[u8]>) -> Result<u16> { | ||
| 83 | let mut buf = [0u8; 2]; | ||
| 84 | cursor.read_exact(&mut buf)?; | ||
| 85 | Ok(u16::from_be_bytes(buf)) | ||
| 86 | } | ||
| 87 | |||
| 88 | fn read_u32(cursor: &mut Cursor<&[u8]>) -> Result<u32> { | ||
| 89 | let mut buf = [0u8; 4]; | ||
| 90 | cursor.read_exact(&mut buf)?; | ||
| 91 | Ok(u32::from_be_bytes(buf)) | ||
| 92 | } | ||
| 93 | |||
| 94 | fn read_arr<const N: usize>(cursor: &mut Cursor<&[u8]>) -> Result<[u8; N]> { | ||
| 95 | let mut buf = [0u8; N]; | ||
| 96 | cursor.read_exact(&mut buf)?; | ||
| 97 | Ok(buf) | ||
| 98 | } | ||
| 99 | |||
| 100 | fn read_null_terminated_vec(cursor: &mut Cursor<&[u8]>) -> Result<Vec<u8>> { | ||
| 101 | let mut buf = Vec::default(); | ||
| 102 | cursor.read_until(0, &mut buf)?; | ||
| 103 | buf.pop(); | ||
| 104 | Ok(buf) | ||
| 105 | } | ||
| 106 | |||
| 107 | fn read_null_terminated_string(cursor: &mut Cursor<&[u8]>) -> Result<String> { | ||
| 108 | let buf = read_null_terminated_vec(cursor)?; | ||
| 109 | Ok(String::from_utf8(buf).unwrap()) | ||
| 110 | } | ||
| 111 | |||
| 112 | fn read_len8_prefixed_vec(cursor: &mut Cursor<&[u8]>) -> Result<Vec<u8>> { | ||
| 113 | let len = read_u8(cursor)?; | ||
| 114 | let mut buf = vec![0u8; len as usize]; | ||
| 115 | cursor.read_exact(&mut buf)?; | ||
| 116 | Ok(buf) | ||
| 117 | } | ||
| 118 | |||
| 119 | fn read_len8_prefixed_string(cursor: &mut Cursor<&[u8]>) -> Result<String> { | ||
| 120 | let buf = read_len8_prefixed_vec(cursor)?; | ||
| 121 | Ok(String::from_utf8(buf).unwrap()) | ||
| 122 | } | ||
| 123 | |||
| 124 | fn read_ipv4(cursor: &mut Cursor<&[u8]>) -> Result<Ipv4Addr> { | ||
| 125 | Ok(Ipv4Addr::from_octets(read_arr(cursor)?)) | ||
| 126 | } | ||
| 127 | |||
| 128 | fn read_op(cursor: &mut Cursor<&[u8]>) -> Result<BootOp> { | ||
| 129 | let v = read_u8(cursor)?; | ||
| 130 | match v { | ||
| 131 | BootOp::OP_REQUEST => Ok(BootOp::Request), | ||
| 132 | BootOp::OP_REPLY => Ok(BootOp::Reply), | ||
| 133 | _ => Err(std::io::Error::new( | ||
| 134 | std::io::ErrorKind::InvalidData, | ||
| 135 | "invalid boot op", | ||
| 136 | )), | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | fn read_htype(cursor: &mut Cursor<&[u8]>) -> Result<HardwareType> { | ||
| 141 | match read_u8(cursor)? { | ||
| 142 | HardwareType::TYPE_ETHER => Ok(HardwareType::Ethernet), | ||
| 143 | _ => Err(std::io::Error::new( | ||
| 144 | std::io::ErrorKind::InvalidData, | ||
| 145 | "invalid hardware type", | ||
| 146 | )), | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | fn read_option(cursor: &mut Cursor<&[u8]>) -> Result<DhcpOption> { | ||
| 151 | let code = read_u8(cursor)?; | ||
| 152 | Ok(match code { | ||
| 153 | OPTION_CODE_PAD => DhcpOption::Pad, | ||
| 154 | OPTION_CODE_END => DhcpOption::End, | ||
| 155 | OPTION_CODE_VENDOR_CLASS_IDENTIFIER => { | ||
| 156 | DhcpOption::VendorClassIdentifier(read_len8_prefixed_string(cursor)?) | ||
| 157 | } | ||
| 158 | OPTION_CODE_USER_CLASS_INFORMATION => { | ||
| 159 | DhcpOption::UserClassInformation(read_len8_prefixed_string(cursor)?) | ||
| 160 | } | ||
| 161 | _ => { | ||
| 162 | let len = read_u8(cursor)?; | ||
| 163 | let mut data = vec![0u8; usize::from(len)]; | ||
| 164 | cursor.read_exact(&mut data)?; | ||
| 165 | DhcpOption::Unknown { code, data } | ||
| 166 | } | ||
| 167 | }) | ||
| 168 | } | ||
| 169 | |||
| 170 | fn parse_packet(buf: &[u8]) -> Result<DhcpPacket> { | ||
| 171 | let mut cursor = Cursor::new(buf); | ||
| 172 | let mut packet = DhcpPacket { | ||
| 173 | op: read_op(&mut cursor)?, | ||
| 174 | htype: read_htype(&mut cursor)?, | ||
| 175 | hlen: read_u8(&mut cursor)?, | ||
| 176 | hops: read_u8(&mut cursor)?, | ||
| 177 | xid: read_u32(&mut cursor)?, | ||
| 178 | secs: read_u16(&mut cursor)?, | ||
| 179 | flags: read_u16(&mut cursor)?, | ||
| 180 | ciaddr: read_ipv4(&mut cursor)?, | ||
| 181 | yiaddr: read_ipv4(&mut cursor)?, | ||
| 182 | siaddr: read_ipv4(&mut cursor)?, | ||
| 183 | giaddr: read_ipv4(&mut cursor)?, | ||
| 184 | chaddr: read_arr(&mut cursor)?, | ||
| 185 | sname: read_arr(&mut cursor)?, | ||
| 186 | file: read_arr(&mut cursor)?, | ||
| 187 | options: Default::default(), | ||
| 188 | }; | ||
| 189 | |||
| 190 | let magic = read_arr::<4>(&mut cursor)?; | ||
| 191 | assert_eq!(magic, MAGIC_COOKIE); | ||
| 192 | |||
| 193 | while cursor.position() < buf.len() as u64 { | ||
| 194 | let option = read_option(&mut cursor)?; | ||
| 195 | packet.options.push(option); | ||
| 196 | } | ||
| 197 | |||
| 198 | Ok(packet) | ||
| 199 | } | ||
| 200 | |||
| 201 | fn write_buf(writer: &mut Vec<u8>, buf: &[u8]) -> Result<()> { | ||
| 202 | writer.write_all(buf) | ||
| 203 | } | ||
| 204 | |||
| 205 | fn write_u8(writer: &mut Vec<u8>, v: u8) -> Result<()> { | ||
| 206 | write_buf(writer, &[v]) | ||
| 207 | } | ||
| 208 | |||
| 209 | fn write_u16(writer: &mut Vec<u8>, v: u16) -> Result<()> { | ||
| 210 | let buf = u16::to_be_bytes(v); | ||
| 211 | write_buf(writer, &buf) | ||
| 212 | } | ||
| 213 | |||
| 214 | fn write_u32(writer: &mut Vec<u8>, v: u32) -> Result<()> { | ||
| 215 | let buf = u32::to_be_bytes(v); | ||
| 216 | write_buf(writer, &buf) | ||
| 217 | } | ||
| 218 | |||
| 219 | fn write_ipv4(writer: &mut Vec<u8>, v: Ipv4Addr) -> Result<()> { | ||
| 220 | write_buf(writer, &v.octets()) | ||
| 221 | } | ||
| 222 | |||
| 223 | fn write_boot_packet( | ||
| 224 | xid: u32, | ||
| 225 | chaddr: [u8; 16], | ||
| 226 | client_uuid: Option<Vec<u8>>, | ||
| 227 | ipxe: bool, | ||
| 228 | ) -> Result<Vec<u8>> { | ||
| 229 | let mut writer = Vec::default(); | ||
| 230 | write_u8(&mut writer, BootOp::OP_REPLY)?; | ||
| 231 | write_u8(&mut writer, HardwareType::TYPE_ETHER)?; | ||
| 232 | write_u8(&mut writer, 6)?; | ||
| 233 | write_u8(&mut writer, 0)?; | ||
| 234 | write_u32(&mut writer, xid)?; | ||
| 235 | write_u16(&mut writer, 0)?; | ||
| 236 | write_u16(&mut writer, FLAG_BROADCAST)?; | ||
| 237 | write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // ciaddr | ||
| 238 | write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // yiaddr | ||
| 239 | write_ipv4(&mut writer, LOCAL_IPV4)?; // siaddr (TFTP server) | ||
| 240 | write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // giaddr | ||
| 241 | write_buf(&mut writer, &chaddr)?; | ||
| 242 | write_buf(&mut writer, &[0u8; 64])?; | ||
| 243 | write_buf(&mut writer, &[0u8; 128])?; | ||
| 244 | write_buf(&mut writer, &MAGIC_COOKIE)?; | ||
| 245 | |||
| 246 | // Option 53: DHCP Message Type (DHCPOFFER) | ||
| 247 | write_u8(&mut writer, 53)?; | ||
| 248 | write_u8(&mut writer, 1)?; | ||
| 249 | write_u8(&mut writer, 2)?; // DHCPOFFER | ||
| 250 | |||
| 251 | // Option 54: DHCP Server Identifier | ||
| 252 | write_u8(&mut writer, 54)?; | ||
| 253 | write_u8(&mut writer, 4)?; | ||
| 254 | write_ipv4(&mut writer, LOCAL_IPV4)?; // Your server IP | ||
| 255 | |||
| 256 | // Option 60: Vendor Class Identifier | ||
| 257 | const PXE_CLIENT: &[u8] = b"PXEClient"; | ||
| 258 | write_u8(&mut writer, 60)?; | ||
| 259 | write_u8(&mut writer, PXE_CLIENT.len() as u8)?; | ||
| 260 | write_buf(&mut writer, PXE_CLIENT)?; | ||
| 261 | |||
| 262 | // Option 97: Client Machine Identifier (UUID from client) | ||
| 263 | if let Some(uuid) = client_uuid { | ||
| 264 | write_u8(&mut writer, 97)?; | ||
| 265 | write_u8(&mut writer, uuid.len() as u8)?; | ||
| 266 | write_buf(&mut writer, &uuid)?; | ||
| 267 | } | ||
| 268 | |||
| 269 | // TFTP server name | ||
| 270 | const SERVER_NAME: &[u8] = b"diogos-air"; | ||
| 271 | write_u8(&mut writer, 66)?; | ||
| 272 | write_u8(&mut writer, SERVER_NAME.len() as u8)?; | ||
| 273 | write_buf(&mut writer, SERVER_NAME)?; | ||
| 274 | |||
| 275 | write_u8(&mut writer, 67)?; | ||
| 276 | if !ipxe { | ||
| 277 | write_u8(&mut writer, BOOT_FILE_NAME.len() as u8)?; | ||
| 278 | write_buf(&mut writer, BOOT_FILE_NAME)?; | ||
| 279 | } else { | ||
| 280 | write_u8(&mut writer, BOOT_FILE_NAME_IPXE.len() as u8)?; | ||
| 281 | write_buf(&mut writer, BOOT_FILE_NAME_IPXE)?; | ||
| 282 | } | ||
| 283 | |||
| 284 | // Option 255: End | ||
| 285 | write_u8(&mut writer, 255)?; | ||
| 286 | |||
| 287 | Ok(writer) | ||
| 288 | } | ||
| 289 | |||
| 290 | fn write_boot_ack(xid: u32, chaddr: [u8; 16], client_uuid: Option<Vec<u8>>) -> Result<Vec<u8>> { | ||
| 291 | let mut writer = Vec::default(); | ||
| 292 | write_u8(&mut writer, BootOp::OP_REPLY)?; | ||
| 293 | write_u8(&mut writer, HardwareType::TYPE_ETHER)?; | ||
| 294 | write_u8(&mut writer, 6)?; | ||
| 295 | write_u8(&mut writer, 0)?; | ||
| 296 | write_u32(&mut writer, xid)?; | ||
| 297 | write_u16(&mut writer, 0)?; | ||
| 298 | write_u16(&mut writer, 0)?; | ||
| 299 | write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // ciaddr | ||
| 300 | write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // yiaddr | ||
| 301 | write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // siaddr (TFTP server) | ||
| 302 | write_ipv4(&mut writer, Ipv4Addr::UNSPECIFIED)?; // giaddr | ||
| 303 | write_buf(&mut writer, &chaddr)?; | ||
| 304 | write_buf(&mut writer, &[0u8; 64])?; | ||
| 305 | write_buf(&mut writer, &[0u8; 128])?; | ||
| 306 | write_buf(&mut writer, &MAGIC_COOKIE)?; | ||
| 307 | |||
| 308 | // Option 53: DHCP Message Type (DHCPOFFER) | ||
| 309 | write_u8(&mut writer, 53)?; | ||
| 310 | write_u8(&mut writer, 1)?; | ||
| 311 | write_u8(&mut writer, 5)?; // DHCPACK | ||
| 312 | |||
| 313 | // Option 54: DHCP Server Identifier | ||
| 314 | write_u8(&mut writer, 54)?; | ||
| 315 | write_u8(&mut writer, 4)?; | ||
| 316 | write_ipv4(&mut writer, LOCAL_IPV4)?; // Your server IP | ||
| 317 | |||
| 318 | // Option 60: Vendor Class Identifier | ||
| 319 | const PXE_CLIENT: &[u8] = b"PXEClient"; | ||
| 320 | write_u8(&mut writer, 60)?; | ||
| 321 | write_u8(&mut writer, PXE_CLIENT.len() as u8)?; | ||
| 322 | write_buf(&mut writer, PXE_CLIENT)?; | ||
| 323 | |||
| 324 | // Option 97: Client Machine Identifier (UUID from client) | ||
| 325 | if let Some(uuid) = client_uuid { | ||
| 326 | write_u8(&mut writer, 97)?; | ||
| 327 | write_u8(&mut writer, uuid.len() as u8)?; | ||
| 328 | write_buf(&mut writer, &uuid)?; | ||
| 329 | } | ||
| 330 | |||
| 331 | // TFTP server name | ||
| 332 | const SERVER_NAME: &[u8] = b"diogos-air"; | ||
| 333 | write_u8(&mut writer, 66)?; | ||
| 334 | write_u8(&mut writer, SERVER_NAME.len() as u8)?; | ||
| 335 | write_buf(&mut writer, SERVER_NAME)?; | ||
| 336 | |||
| 337 | // TFTP file name | ||
| 338 | write_u8(&mut writer, 67)?; | ||
| 339 | write_u8(&mut writer, BOOT_FILE_NAME.len() as u8)?; | ||
| 340 | write_buf(&mut writer, BOOT_FILE_NAME)?; | ||
| 341 | |||
| 342 | write_u8(&mut writer, 71)?; | ||
| 343 | write_u8(&mut writer, 4)?; | ||
| 344 | write_buf(&mut writer, &[0, 0, 0, 0])?; | ||
| 345 | |||
| 346 | // Option 255: End | ||
| 347 | write_u8(&mut writer, 255)?; | ||
| 348 | |||
| 349 | Ok(writer) | ||
| 350 | } | ||
| 351 | 17 | ||
| 352 | #[derive(Debug, Clone)] | 18 | #[derive(Debug, Clone)] |
| 353 | struct InterfaceAddr { | 19 | struct InterfaceAddr { |
| @@ -433,7 +99,7 @@ fn main() { | |||
| 433 | } | 99 | } |
| 434 | 100 | ||
| 435 | fn handle_packet(buf: &[u8], socket: &UdpSocket) { | 101 | fn handle_packet(buf: &[u8], socket: &UdpSocket) { |
| 436 | match parse_packet(buf) { | 102 | match dhcp::parse_packet(buf) { |
| 437 | Ok(packet) => { | 103 | Ok(packet) => { |
| 438 | println!("Parsed DHCP packet: XID={:08x}", packet.xid); | 104 | println!("Parsed DHCP packet: XID={:08x}", packet.xid); |
| 439 | 105 | ||
| @@ -464,10 +130,21 @@ fn handle_packet(buf: &[u8], socket: &UdpSocket) { | |||
| 464 | 130 | ||
| 465 | if is_pxe { | 131 | if is_pxe { |
| 466 | println!("Responding to PXE client with DHCPOFFER"); | 132 | println!("Responding to PXE client with DHCPOFFER"); |
| 467 | let response = | 133 | let mut response_buf = Vec::default(); |
| 468 | write_boot_packet(packet.xid, packet.chaddr, client_uuid, is_ipxe).unwrap(); | 134 | let response = DhcpPacket::new_boot( |
| 135 | packet.xid, | ||
| 136 | packet.chaddr, | ||
| 137 | client_uuid.unwrap(), | ||
| 138 | LOCAL_IPV4, | ||
| 139 | LOCAL_HOSTNAME.to_string(), | ||
| 140 | match is_ipxe { | ||
| 141 | true => "test.ipxe".to_string(), | ||
| 142 | false => "ipxe.efi".to_string(), | ||
| 143 | }, | ||
| 144 | ); | ||
| 145 | response.write(&mut response_buf).unwrap(); | ||
| 469 | socket | 146 | socket |
| 470 | .send_to(&response, SocketAddrV4::new(Ipv4Addr::BROADCAST, 68)) | 147 | .send_to(&response_buf, SocketAddrV4::new(Ipv4Addr::BROADCAST, 68)) |
| 471 | .unwrap(); | 148 | .unwrap(); |
| 472 | } else { | 149 | } else { |
| 473 | println!("Not a PXE client, ignoring"); | 150 | println!("Not a PXE client, ignoring"); |
| @@ -480,7 +157,7 @@ fn handle_packet(buf: &[u8], socket: &UdpSocket) { | |||
| 480 | } | 157 | } |
| 481 | 158 | ||
| 482 | fn handle_packet_4011(buf: &[u8], socket: &UdpSocket, sender_addr: SocketAddr) { | 159 | fn handle_packet_4011(buf: &[u8], socket: &UdpSocket, sender_addr: SocketAddr) { |
| 483 | match parse_packet(buf) { | 160 | match dhcp::parse_packet(buf) { |
| 484 | Ok(packet) => { | 161 | Ok(packet) => { |
| 485 | println!("Parsed DHCP packet on 4011: XID={:08x}", packet.xid); | 162 | println!("Parsed DHCP packet on 4011: XID={:08x}", packet.xid); |
| 486 | 163 | ||
| @@ -494,8 +171,17 @@ fn handle_packet_4011(buf: &[u8], socket: &UdpSocket, sender_addr: SocketAddr) { | |||
| 494 | } | 171 | } |
| 495 | 172 | ||
| 496 | println!("Responding with DHCPACK"); | 173 | println!("Responding with DHCPACK"); |
| 497 | let response = write_boot_ack(packet.xid, packet.chaddr, client_uuid).unwrap(); | 174 | let mut response_buf = Vec::default(); |
| 498 | socket.send_to(&response, sender_addr).unwrap(); | 175 | let response = DhcpPacket::new_boot_ack( |
| 176 | packet.xid, | ||
| 177 | packet.chaddr, | ||
| 178 | client_uuid.unwrap(), | ||
| 179 | LOCAL_IPV4, | ||
| 180 | LOCAL_HOSTNAME.to_string(), | ||
| 181 | "ipxe.efi".to_string(), | ||
| 182 | ); | ||
| 183 | response.write(&mut response_buf).unwrap(); | ||
| 184 | socket.send_to(&response_buf, sender_addr).unwrap(); | ||
| 499 | } | 185 | } |
| 500 | Err(e) => { | 186 | Err(e) => { |
| 501 | println!("Failed to parse packet on 4011: {}", e); | 187 | println!("Failed to parse packet on 4011: {}", e); |
