diff options
| author | diogo464 <[email protected]> | 2025-10-11 11:34:59 +0100 |
|---|---|---|
| committer | diogo464 <[email protected]> | 2025-10-11 11:34:59 +0100 |
| commit | 521218ce06fbb7bd518eb6a069406936079e3ec2 (patch) | |
| tree | 862e84ec23175119abb7652e197a4113e7fcc31b /src/dhcp.rs | |
| parent | 89db26c86bf48a4c527778fc254765a38b7e9085 (diff) | |
initial working version
Diffstat (limited to 'src/dhcp.rs')
| -rw-r--r-- | src/dhcp.rs | 398 |
1 files changed, 369 insertions, 29 deletions
diff --git a/src/dhcp.rs b/src/dhcp.rs index 38cc8e4..51680d1 100644 --- a/src/dhcp.rs +++ b/src/dhcp.rs | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | use std::{ | 1 | use std::{ |
| 2 | io::{Cursor, Read as _, Result, Write}, | 2 | io::{Cursor, Read as _, Result, Write}, |
| 3 | net::Ipv4Addr, | 3 | net::Ipv4Addr, |
| 4 | str::FromStr, | ||
| 4 | }; | 5 | }; |
| 5 | 6 | ||
| 6 | use crate::wire; | 7 | use crate::wire; |
| @@ -8,6 +9,11 @@ use crate::wire; | |||
| 8 | const MAGIC_COOKIE: [u8; 4] = [0x63, 0x82, 0x53, 0x63]; | 9 | const MAGIC_COOKIE: [u8; 4] = [0x63, 0x82, 0x53, 0x63]; |
| 9 | const FLAG_BROADCAST: u16 = 1 << 15; | 10 | const FLAG_BROADCAST: u16 = 1 << 15; |
| 10 | 11 | ||
| 12 | pub const VENDOR_CLASS_PXE_CLIENT: &'static [u8] = b"PXEClient"; | ||
| 13 | pub const VENDOR_CLASS_PXE_SERVER: &'static [u8] = b"PXEServer"; | ||
| 14 | |||
| 15 | pub const USER_CLASS_IPXE: &'static [u8] = b"iPXE"; | ||
| 16 | |||
| 11 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] | 17 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] |
| 12 | pub enum BootOp { | 18 | pub enum BootOp { |
| 13 | #[default] | 19 | #[default] |
| @@ -29,6 +35,8 @@ impl From<BootOp> for u8 { | |||
| 29 | } | 35 | } |
| 30 | } | 36 | } |
| 31 | 37 | ||
| 38 | pub type HardwareAddress = [u8; 16]; | ||
| 39 | |||
| 32 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] | 40 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] |
| 33 | pub enum HardwareType { | 41 | pub enum HardwareType { |
| 34 | #[default] | 42 | #[default] |
| @@ -78,10 +86,11 @@ pub enum DhcpOption { | |||
| 78 | End, | 86 | End, |
| 79 | MessageType(DhcpMessageType), | 87 | MessageType(DhcpMessageType), |
| 80 | ServerIdentifier(Ipv4Addr), | 88 | ServerIdentifier(Ipv4Addr), |
| 81 | VendorClassIdentifier(String), | 89 | VendorClassIdentifier(Vec<u8>), |
| 82 | TftpServerName(String), | 90 | TftpServerName(String), |
| 83 | TftpFileName(String), | 91 | TftpFileName(String), |
| 84 | UserClassInformation(String), | 92 | UserClassInformation(Vec<u8>), |
| 93 | ClientSystemArchitecture(SystemArchitecture), | ||
| 85 | ClientMachineIdentifier(Vec<u8>), | 94 | ClientMachineIdentifier(Vec<u8>), |
| 86 | Unknown { code: u8, data: Vec<u8> }, | 95 | Unknown { code: u8, data: Vec<u8> }, |
| 87 | } | 96 | } |
| @@ -95,6 +104,7 @@ impl DhcpOption { | |||
| 95 | pub const CODE_TFTP_SERVER_NAME: u8 = 66; | 104 | pub const CODE_TFTP_SERVER_NAME: u8 = 66; |
| 96 | pub const CODE_TFTP_FILE_NAME: u8 = 67; | 105 | pub const CODE_TFTP_FILE_NAME: u8 = 67; |
| 97 | pub const CODE_USER_CLASS_INFORMATION: u8 = 77; | 106 | pub const CODE_USER_CLASS_INFORMATION: u8 = 77; |
| 107 | pub const CODE_CLIENT_SYSTEM_ARCHITECTURE: u8 = 93; | ||
| 98 | pub const CODE_CLIENT_MACHINE_IDENTIFIER: u8 = 97; | 108 | pub const CODE_CLIENT_MACHINE_IDENTIFIER: u8 = 97; |
| 99 | 109 | ||
| 100 | pub fn code(&self) -> u8 { | 110 | pub fn code(&self) -> u8 { |
| @@ -107,12 +117,294 @@ impl DhcpOption { | |||
| 107 | DhcpOption::TftpServerName(_) => Self::CODE_TFTP_SERVER_NAME, | 117 | DhcpOption::TftpServerName(_) => Self::CODE_TFTP_SERVER_NAME, |
| 108 | DhcpOption::TftpFileName(_) => Self::CODE_TFTP_FILE_NAME, | 118 | DhcpOption::TftpFileName(_) => Self::CODE_TFTP_FILE_NAME, |
| 109 | DhcpOption::UserClassInformation(_) => Self::CODE_USER_CLASS_INFORMATION, | 119 | DhcpOption::UserClassInformation(_) => Self::CODE_USER_CLASS_INFORMATION, |
| 120 | DhcpOption::ClientSystemArchitecture(_) => Self::CODE_CLIENT_SYSTEM_ARCHITECTURE, | ||
| 110 | DhcpOption::ClientMachineIdentifier(_) => Self::CODE_CLIENT_MACHINE_IDENTIFIER, | 121 | DhcpOption::ClientMachineIdentifier(_) => Self::CODE_CLIENT_MACHINE_IDENTIFIER, |
| 111 | DhcpOption::Unknown { code, .. } => *code, | 122 | DhcpOption::Unknown { code, .. } => *code, |
| 112 | } | 123 | } |
| 113 | } | 124 | } |
| 114 | } | 125 | } |
| 115 | 126 | ||
| 127 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| 128 | pub enum SystemArchitecture { | ||
| 129 | IntelX86pc, | ||
| 130 | NECPC98, | ||
| 131 | EfiItanium, | ||
| 132 | DecAlpha, | ||
| 133 | ArcX86, | ||
| 134 | IntelLeanClient, | ||
| 135 | EfiIA32, | ||
| 136 | EfiBC, | ||
| 137 | EfiXscale, | ||
| 138 | EfiX86_64, | ||
| 139 | EfiARM32, | ||
| 140 | EfiARM64, | ||
| 141 | EfiARM32Http, | ||
| 142 | EfiARM64Http, | ||
| 143 | ARM32Uboot, | ||
| 144 | ARM64Uboot, | ||
| 145 | Unknown(u16), | ||
| 146 | } | ||
| 147 | |||
| 148 | impl SystemArchitecture { | ||
| 149 | pub const CODE_INTEL_X86_PC: u16 = 0; | ||
| 150 | pub const CODE_NEC_PC98: u16 = 1; | ||
| 151 | pub const CODE_EFI_ITANIUM: u16 = 2; | ||
| 152 | pub const CODE_DEC_ALPHA: u16 = 3; | ||
| 153 | pub const CODE_ARC_X86: u16 = 4; | ||
| 154 | pub const CODE_INTEL_LEAN_CLIENT: u16 = 5; | ||
| 155 | pub const CODE_EFI_IA32: u16 = 6; | ||
| 156 | pub const CODE_EFI_BC: u16 = 7; | ||
| 157 | pub const CODE_EFI_XSCALE: u16 = 8; | ||
| 158 | pub const CODE_EFI_X86_64: u16 = 9; | ||
| 159 | pub const CODE_EFI_ARM32: u16 = 10; | ||
| 160 | pub const CODE_EFI_ARM64: u16 = 11; | ||
| 161 | pub const CODE_EFI_ARM32_HTTP: u16 = 18; | ||
| 162 | pub const CODE_EFI_ARM64_HTTP: u16 = 19; | ||
| 163 | pub const CODE_ARM32_UBOOT: u16 = 21; | ||
| 164 | pub const CODE_ARM64_UBOOT: u16 = 22; | ||
| 165 | } | ||
| 166 | |||
| 167 | impl From<u16> for SystemArchitecture { | ||
| 168 | fn from(value: u16) -> Self { | ||
| 169 | match value { | ||
| 170 | Self::CODE_INTEL_X86_PC => SystemArchitecture::IntelX86pc, | ||
| 171 | Self::CODE_NEC_PC98 => SystemArchitecture::NECPC98, | ||
| 172 | Self::CODE_EFI_ITANIUM => SystemArchitecture::EfiItanium, | ||
| 173 | Self::CODE_DEC_ALPHA => SystemArchitecture::DecAlpha, | ||
| 174 | Self::CODE_ARC_X86 => SystemArchitecture::ArcX86, | ||
| 175 | Self::CODE_INTEL_LEAN_CLIENT => SystemArchitecture::IntelLeanClient, | ||
| 176 | Self::CODE_EFI_IA32 => SystemArchitecture::EfiIA32, | ||
| 177 | Self::CODE_EFI_BC => SystemArchitecture::EfiBC, | ||
| 178 | Self::CODE_EFI_XSCALE => SystemArchitecture::EfiXscale, | ||
| 179 | Self::CODE_EFI_X86_64 => SystemArchitecture::EfiX86_64, | ||
| 180 | Self::CODE_EFI_ARM32 => SystemArchitecture::EfiARM32, | ||
| 181 | Self::CODE_EFI_ARM64 => SystemArchitecture::EfiARM64, | ||
| 182 | Self::CODE_EFI_ARM32_HTTP => SystemArchitecture::EfiARM32Http, | ||
| 183 | Self::CODE_EFI_ARM64_HTTP => SystemArchitecture::EfiARM64Http, | ||
| 184 | Self::CODE_ARM32_UBOOT => SystemArchitecture::ARM32Uboot, | ||
| 185 | Self::CODE_ARM64_UBOOT => SystemArchitecture::ARM64Uboot, | ||
| 186 | _ => SystemArchitecture::Unknown(value), | ||
| 187 | } | ||
| 188 | } | ||
| 189 | } | ||
| 190 | |||
| 191 | impl From<SystemArchitecture> for u16 { | ||
| 192 | fn from(value: SystemArchitecture) -> Self { | ||
| 193 | match value { | ||
| 194 | SystemArchitecture::IntelX86pc => SystemArchitecture::CODE_INTEL_X86_PC, | ||
| 195 | SystemArchitecture::NECPC98 => SystemArchitecture::CODE_NEC_PC98, | ||
| 196 | SystemArchitecture::EfiItanium => SystemArchitecture::CODE_EFI_ITANIUM, | ||
| 197 | SystemArchitecture::DecAlpha => SystemArchitecture::CODE_DEC_ALPHA, | ||
| 198 | SystemArchitecture::ArcX86 => SystemArchitecture::CODE_ARC_X86, | ||
| 199 | SystemArchitecture::IntelLeanClient => SystemArchitecture::CODE_INTEL_LEAN_CLIENT, | ||
| 200 | SystemArchitecture::EfiIA32 => SystemArchitecture::CODE_EFI_IA32, | ||
| 201 | SystemArchitecture::EfiBC => SystemArchitecture::CODE_EFI_BC, | ||
| 202 | SystemArchitecture::EfiXscale => SystemArchitecture::CODE_EFI_XSCALE, | ||
| 203 | SystemArchitecture::EfiX86_64 => SystemArchitecture::CODE_EFI_X86_64, | ||
| 204 | SystemArchitecture::EfiARM32 => SystemArchitecture::CODE_EFI_ARM32, | ||
| 205 | SystemArchitecture::EfiARM64 => SystemArchitecture::CODE_EFI_ARM64, | ||
| 206 | SystemArchitecture::EfiARM32Http => SystemArchitecture::CODE_EFI_ARM32_HTTP, | ||
| 207 | SystemArchitecture::EfiARM64Http => SystemArchitecture::CODE_EFI_ARM64_HTTP, | ||
| 208 | SystemArchitecture::ARM32Uboot => SystemArchitecture::CODE_ARM32_UBOOT, | ||
| 209 | SystemArchitecture::ARM64Uboot => SystemArchitecture::CODE_ARM64_UBOOT, | ||
| 210 | SystemArchitecture::Unknown(code) => code, | ||
| 211 | } | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | impl FromStr for SystemArchitecture { | ||
| 216 | type Err = <u16 as FromStr>::Err; | ||
| 217 | |||
| 218 | fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { | ||
| 219 | s.parse::<u16>().map(From::from) | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | #[derive(Debug)] | ||
| 224 | pub struct InvalidPxeClassIdentifierKind(String); | ||
| 225 | |||
| 226 | impl InvalidPxeClassIdentifierKind { | ||
| 227 | fn new(kind: impl Into<String>) -> Self { | ||
| 228 | Self(kind.into()) | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | impl std::fmt::Display for InvalidPxeClassIdentifierKind { | ||
| 233 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 234 | write!( | ||
| 235 | f, | ||
| 236 | "invalid pxe class identifier kind '{}', expected 'PXEClient' or 'PXEServer'", | ||
| 237 | self.0 | ||
| 238 | ) | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | impl std::error::Error for InvalidPxeClassIdentifierKind {} | ||
| 243 | |||
| 244 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| 245 | pub enum PxeClassIdentifierKind { | ||
| 246 | Client, | ||
| 247 | Server, | ||
| 248 | } | ||
| 249 | |||
| 250 | impl PxeClassIdentifierKind { | ||
| 251 | pub const KIND_CLIENT: &'static str = "PXEClient"; | ||
| 252 | pub const KIND_SERVER: &'static str = "PXEServer"; | ||
| 253 | } | ||
| 254 | |||
| 255 | impl FromStr for PxeClassIdentifierKind { | ||
| 256 | type Err = InvalidPxeClassIdentifierKind; | ||
| 257 | |||
| 258 | fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { | ||
| 259 | match s { | ||
| 260 | Self::KIND_CLIENT => Ok(Self::Client), | ||
| 261 | Self::KIND_SERVER => Ok(Self::Server), | ||
| 262 | _ => Err(InvalidPxeClassIdentifierKind::new(s)), | ||
| 263 | } | ||
| 264 | } | ||
| 265 | } | ||
| 266 | |||
| 267 | #[derive(Debug)] | ||
| 268 | pub struct InvalidPxeClassIdentifier(String, String); | ||
| 269 | |||
| 270 | impl InvalidPxeClassIdentifier { | ||
| 271 | fn new(class: impl Into<String>, reason: impl Into<String>) -> Self { | ||
| 272 | Self(class.into(), reason.into()) | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | impl std::fmt::Display for InvalidPxeClassIdentifier { | ||
| 277 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 278 | write!(f, "invalid pxe class identifier '{}': {}", self.0, self.1) | ||
| 279 | } | ||
| 280 | } | ||
| 281 | |||
| 282 | impl std::error::Error for InvalidPxeClassIdentifier {} | ||
| 283 | |||
| 284 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| 285 | pub enum PxeClassIdentifier { | ||
| 286 | Client(PxeClassIdentifierClient), | ||
| 287 | Server(PxeClassIdentifierServer), | ||
| 288 | } | ||
| 289 | |||
| 290 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| 291 | pub struct PxeClassIdentifierServer; | ||
| 292 | |||
| 293 | impl std::fmt::Display for PxeClassIdentifierServer { | ||
| 294 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 295 | f.write_str("PXEServer") | ||
| 296 | } | ||
| 297 | } | ||
| 298 | |||
| 299 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| 300 | pub struct PxeClassIdentifierClient { | ||
| 301 | pub architecture: SystemArchitecture, | ||
| 302 | pub undi_major: u16, | ||
| 303 | pub undi_minor: u16, | ||
| 304 | } | ||
| 305 | |||
| 306 | impl std::fmt::Display for PxeClassIdentifierClient { | ||
| 307 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 308 | write!( | ||
| 309 | f, | ||
| 310 | "PXEClient:Arch:{:05}:UNDI:{:03}{:03}", | ||
| 311 | u16::from(self.architecture), | ||
| 312 | self.undi_major, | ||
| 313 | self.undi_minor | ||
| 314 | ) | ||
| 315 | } | ||
| 316 | } | ||
| 317 | |||
| 318 | impl std::fmt::Display for PxeClassIdentifier { | ||
| 319 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 320 | match self { | ||
| 321 | PxeClassIdentifier::Client(client) => client.fmt(f), | ||
| 322 | PxeClassIdentifier::Server(server) => server.fmt(f), | ||
| 323 | } | ||
| 324 | } | ||
| 325 | } | ||
| 326 | |||
| 327 | impl TryFrom<&[u8]> for PxeClassIdentifier { | ||
| 328 | type Error = InvalidPxeClassIdentifier; | ||
| 329 | |||
| 330 | fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> { | ||
| 331 | let str = std::str::from_utf8(value).map_err(|err| { | ||
| 332 | InvalidPxeClassIdentifier::new( | ||
| 333 | format!("{value:?}"), | ||
| 334 | format!("invalid utf-8 string: {err}"), | ||
| 335 | ) | ||
| 336 | })?; | ||
| 337 | str.parse() | ||
| 338 | } | ||
| 339 | } | ||
| 340 | |||
| 341 | impl FromStr for PxeClassIdentifier { | ||
| 342 | type Err = InvalidPxeClassIdentifier; | ||
| 343 | |||
| 344 | fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { | ||
| 345 | let mut parts = s.split(":"); | ||
| 346 | let make_err = |reason: String| InvalidPxeClassIdentifier::new(s, reason); | ||
| 347 | |||
| 348 | let kind = match parts.next() { | ||
| 349 | Some(kind) => kind | ||
| 350 | .parse::<PxeClassIdentifierKind>() | ||
| 351 | .map_err(|err| make_err(err.to_string()))?, | ||
| 352 | None => return Err(make_err("missing class kind".to_string())), | ||
| 353 | }; | ||
| 354 | |||
| 355 | if kind == PxeClassIdentifierKind::Server { | ||
| 356 | if parts.next().is_some() { | ||
| 357 | return Err(make_err("invalid class".to_string())); | ||
| 358 | } | ||
| 359 | return Ok(Self::Server(PxeClassIdentifierServer)); | ||
| 360 | } | ||
| 361 | |||
| 362 | if !parts.next().map(|s| s == "Arch").unwrap_or(false) { | ||
| 363 | return Err(make_err("invalid class".to_string())); | ||
| 364 | } | ||
| 365 | |||
| 366 | let architecture = match parts.next() { | ||
| 367 | Some(arch) => arch | ||
| 368 | .parse::<SystemArchitecture>() | ||
| 369 | .map_err(|err| make_err(err.to_string()))?, | ||
| 370 | None => return Err(make_err("missing architecture".to_string())), | ||
| 371 | }; | ||
| 372 | |||
| 373 | if !parts.next().map(|s| s == "UNDI").unwrap_or(false) { | ||
| 374 | return Err(make_err("invalid class".to_string())); | ||
| 375 | } | ||
| 376 | |||
| 377 | let undi_str = match parts.next() { | ||
| 378 | Some(undi_str) => undi_str, | ||
| 379 | None => return Err(make_err("missing undi version".to_string())), | ||
| 380 | }; | ||
| 381 | |||
| 382 | if undi_str.len() != 6 { | ||
| 383 | return Err(make_err("invalid undi version length".to_string())); | ||
| 384 | } | ||
| 385 | |||
| 386 | let (undi_major_str, undi_minor_str) = undi_str.split_at_checked(3).unwrap(); | ||
| 387 | |||
| 388 | let undi_major = undi_major_str | ||
| 389 | .parse::<u16>() | ||
| 390 | .map_err(|err| make_err(err.to_string()))?; | ||
| 391 | |||
| 392 | let undi_minor = undi_minor_str | ||
| 393 | .parse::<u16>() | ||
| 394 | .map_err(|err| make_err(err.to_string()))?; | ||
| 395 | |||
| 396 | if parts.next().is_some() { | ||
| 397 | return Err(make_err("invalid class".to_string())); | ||
| 398 | } | ||
| 399 | |||
| 400 | Ok(Self::Client(PxeClassIdentifierClient { | ||
| 401 | architecture, | ||
| 402 | undi_major, | ||
| 403 | undi_minor, | ||
| 404 | })) | ||
| 405 | } | ||
| 406 | } | ||
| 407 | |||
| 116 | #[derive(Debug)] | 408 | #[derive(Debug)] |
| 117 | pub struct DhcpPacket { | 409 | pub struct DhcpPacket { |
| 118 | pub op: BootOp, | 410 | pub op: BootOp, |
| @@ -125,7 +417,7 @@ pub struct DhcpPacket { | |||
| 125 | pub yiaddr: Ipv4Addr, | 417 | pub yiaddr: Ipv4Addr, |
| 126 | pub siaddr: Ipv4Addr, | 418 | pub siaddr: Ipv4Addr, |
| 127 | pub giaddr: Ipv4Addr, | 419 | pub giaddr: Ipv4Addr, |
| 128 | pub chaddr: [u8; 16], | 420 | pub chaddr: HardwareAddress, |
| 129 | // server host name | 421 | // server host name |
| 130 | pub sname: String, | 422 | pub sname: String, |
| 131 | // boot file name | 423 | // boot file name |
| @@ -158,11 +450,21 @@ impl DhcpPacket { | |||
| 158 | pub fn new_boot( | 450 | pub fn new_boot( |
| 159 | xid: u32, | 451 | xid: u32, |
| 160 | chaddr: [u8; 16], | 452 | chaddr: [u8; 16], |
| 161 | client_uuid: Vec<u8>, | 453 | client_uuid: Option<Vec<u8>>, |
| 162 | local_ip: Ipv4Addr, | 454 | local_ip: Ipv4Addr, |
| 163 | local_hostname: String, | 455 | local_hostname: String, |
| 164 | filename: String, | 456 | filename: String, |
| 165 | ) -> Self { | 457 | ) -> Self { |
| 458 | let mut options = vec![ | ||
| 459 | DhcpOption::MessageType(DhcpMessageType::Offer), | ||
| 460 | DhcpOption::ServerIdentifier(local_ip), | ||
| 461 | DhcpOption::VendorClassIdentifier(b"PXEClient".to_vec()), | ||
| 462 | DhcpOption::TftpServerName(local_hostname), | ||
| 463 | DhcpOption::TftpFileName(filename), | ||
| 464 | ]; | ||
| 465 | if let Some(uuid) = client_uuid { | ||
| 466 | options.push(DhcpOption::ClientMachineIdentifier(uuid)); | ||
| 467 | } | ||
| 166 | Self { | 468 | Self { |
| 167 | op: BootOp::Reply, | 469 | op: BootOp::Reply, |
| 168 | htype: HardwareType::Ethernet, | 470 | htype: HardwareType::Ethernet, |
| @@ -177,25 +479,28 @@ impl DhcpPacket { | |||
| 177 | chaddr, | 479 | chaddr, |
| 178 | sname: Default::default(), | 480 | sname: Default::default(), |
| 179 | file: Default::default(), | 481 | file: Default::default(), |
| 180 | options: vec![ | 482 | options, |
| 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 | } | 483 | } |
| 189 | } | 484 | } |
| 190 | 485 | ||
| 191 | pub fn new_boot_ack( | 486 | pub fn new_boot_ack( |
| 192 | xid: u32, | 487 | xid: u32, |
| 193 | chaddr: [u8; 16], | 488 | chaddr: [u8; 16], |
| 194 | client_uuid: Vec<u8>, | 489 | client_uuid: Option<Vec<u8>>, |
| 195 | local_ip: Ipv4Addr, | 490 | local_ip: Ipv4Addr, |
| 196 | hostname: String, | 491 | hostname: String, |
| 197 | filename: String, | 492 | filename: String, |
| 198 | ) -> Self { | 493 | ) -> Self { |
| 494 | let mut options = vec![ | ||
| 495 | DhcpOption::MessageType(DhcpMessageType::Ack), | ||
| 496 | DhcpOption::ServerIdentifier(local_ip), | ||
| 497 | DhcpOption::VendorClassIdentifier(b"PXEClient".to_vec()), | ||
| 498 | DhcpOption::TftpServerName(hostname), | ||
| 499 | DhcpOption::TftpFileName(filename), | ||
| 500 | ]; | ||
| 501 | if let Some(uuid) = client_uuid { | ||
| 502 | options.push(DhcpOption::ClientMachineIdentifier(uuid)); | ||
| 503 | } | ||
| 199 | Self { | 504 | Self { |
| 200 | op: BootOp::Reply, | 505 | op: BootOp::Reply, |
| 201 | htype: HardwareType::Ethernet, | 506 | htype: HardwareType::Ethernet, |
| @@ -210,14 +515,7 @@ impl DhcpPacket { | |||
| 210 | chaddr, | 515 | chaddr, |
| 211 | sname: Default::default(), | 516 | sname: Default::default(), |
| 212 | file: Default::default(), | 517 | file: Default::default(), |
| 213 | options: vec![ | 518 | options, |
| 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 | } | 519 | } |
| 222 | } | 520 | } |
| 223 | 521 | ||
| @@ -296,10 +594,23 @@ fn read_option(cursor: &mut Cursor<&[u8]>) -> Result<DhcpOption> { | |||
| 296 | DhcpOption::CODE_PAD => DhcpOption::Pad, | 594 | DhcpOption::CODE_PAD => DhcpOption::Pad, |
| 297 | DhcpOption::CODE_END => DhcpOption::End, | 595 | DhcpOption::CODE_END => DhcpOption::End, |
| 298 | DhcpOption::CODE_VENDOR_CLASS_IDENTIFIER => { | 596 | DhcpOption::CODE_VENDOR_CLASS_IDENTIFIER => { |
| 299 | DhcpOption::VendorClassIdentifier(read_len8_prefixed_string(cursor)?) | 597 | DhcpOption::VendorClassIdentifier(read_len8_prefixed_vec(cursor)?) |
| 300 | } | 598 | } |
| 301 | DhcpOption::CODE_USER_CLASS_INFORMATION => { | 599 | DhcpOption::CODE_USER_CLASS_INFORMATION => { |
| 302 | DhcpOption::UserClassInformation(read_len8_prefixed_string(cursor)?) | 600 | DhcpOption::UserClassInformation(read_len8_prefixed_vec(cursor)?) |
| 601 | } | ||
| 602 | DhcpOption::CODE_CLIENT_SYSTEM_ARCHITECTURE => { | ||
| 603 | let len = read_u8(cursor)?; | ||
| 604 | assert_eq!(len, 2); | ||
| 605 | |||
| 606 | let mut buf = [0u8; 2]; | ||
| 607 | cursor.read_exact(&mut buf)?; | ||
| 608 | |||
| 609 | let arch = SystemArchitecture::from(u16::from_be_bytes(buf)); | ||
| 610 | DhcpOption::ClientSystemArchitecture(arch) | ||
| 611 | } | ||
| 612 | DhcpOption::CODE_CLIENT_MACHINE_IDENTIFIER => { | ||
| 613 | DhcpOption::ClientMachineIdentifier(read_len8_prefixed_vec(cursor)?) | ||
| 303 | } | 614 | } |
| 304 | _ => { | 615 | _ => { |
| 305 | let len = read_u8(cursor)?; | 616 | let len = read_u8(cursor)?; |
| @@ -353,6 +664,20 @@ pub fn parse_packet(buf: &[u8]) -> Result<DhcpPacket> { | |||
| 353 | } | 664 | } |
| 354 | 665 | ||
| 355 | pub fn write_packet<W: Write>(mut writer: W, packet: &DhcpPacket) -> Result<()> { | 666 | pub fn write_packet<W: Write>(mut writer: W, packet: &DhcpPacket) -> Result<()> { |
| 667 | if packet.sname.len() >= 64 { | ||
| 668 | return Err(std::io::Error::new( | ||
| 669 | std::io::ErrorKind::InvalidInput, | ||
| 670 | "sname cannot be longer than 64 bytes", | ||
| 671 | )); | ||
| 672 | } | ||
| 673 | |||
| 674 | if packet.file.len() >= 128 { | ||
| 675 | return Err(std::io::Error::new( | ||
| 676 | std::io::ErrorKind::InvalidInput, | ||
| 677 | "filename cannot be longer than 128 bytes", | ||
| 678 | )); | ||
| 679 | } | ||
| 680 | |||
| 356 | wire::write_u8(&mut writer, u8::from(packet.op))?; | 681 | wire::write_u8(&mut writer, u8::from(packet.op))?; |
| 357 | wire::write_u8(&mut writer, u8::from(packet.htype))?; | 682 | wire::write_u8(&mut writer, u8::from(packet.htype))?; |
| 358 | wire::write_u8(&mut writer, packet.htype.hardware_len())?; | 683 | wire::write_u8(&mut writer, packet.htype.hardware_len())?; |
| @@ -365,10 +690,21 @@ pub fn write_packet<W: Write>(mut writer: W, packet: &DhcpPacket) -> Result<()> | |||
| 365 | wire::write_ipv4(&mut writer, packet.siaddr)?; | 690 | wire::write_ipv4(&mut writer, packet.siaddr)?; |
| 366 | wire::write_ipv4(&mut writer, packet.giaddr)?; | 691 | wire::write_ipv4(&mut writer, packet.giaddr)?; |
| 367 | wire::write(&mut writer, &packet.chaddr)?; | 692 | wire::write(&mut writer, &packet.chaddr)?; |
| 368 | //wire::write_null_terminated_string(&mut writer, &packet.sname)?; | 693 | |
| 369 | //wire::write_null_terminated_string(&mut writer, &packet.file)?; | 694 | let sname_bytes = packet.sname.as_bytes(); |
| 370 | wire::write(&mut writer, &vec![0u8; 64])?; | 695 | wire::write(&mut writer, sname_bytes)?; |
| 371 | wire::write(&mut writer, &vec![0u8; 128])?; | 696 | for _ in 0..(64 - sname_bytes.len()) { |
| 697 | wire::write_u8(&mut writer, 0)?; | ||
| 698 | } | ||
| 699 | |||
| 700 | let file_bytes = packet.file.as_bytes(); | ||
| 701 | wire::write(&mut writer, file_bytes)?; | ||
| 702 | for _ in 0..(128 - file_bytes.len()) { | ||
| 703 | wire::write_u8(&mut writer, 0)?; | ||
| 704 | } | ||
| 705 | |||
| 706 | // wire::write(&mut writer, &vec![0u8; 64])?; | ||
| 707 | // wire::write(&mut writer, &vec![0u8; 128])?; | ||
| 372 | wire::write(&mut writer, &MAGIC_COOKIE)?; | 708 | wire::write(&mut writer, &MAGIC_COOKIE)?; |
| 373 | for option in &packet.options { | 709 | for option in &packet.options { |
| 374 | write_option(&mut writer, option)?; | 710 | write_option(&mut writer, option)?; |
| @@ -390,12 +726,16 @@ pub fn write_option<W: Write>(mut writer: W, option: &DhcpOption) -> Result<()> | |||
| 390 | wire::write_ipv4(&mut writer, *ip)?; | 726 | wire::write_ipv4(&mut writer, *ip)?; |
| 391 | } | 727 | } |
| 392 | DhcpOption::VendorClassIdentifier(vendor_class) => { | 728 | DhcpOption::VendorClassIdentifier(vendor_class) => { |
| 393 | write_option_len_prefixed_string(&mut writer, &vendor_class)? | 729 | write_option_len_prefixed_buf(&mut writer, &vendor_class)? |
| 394 | } | 730 | } |
| 395 | DhcpOption::TftpServerName(name) => write_option_len_prefixed_string(&mut writer, &name)?, | 731 | DhcpOption::TftpServerName(name) => write_option_len_prefixed_string(&mut writer, &name)?, |
| 396 | DhcpOption::TftpFileName(name) => write_option_len_prefixed_string(&mut writer, &name)?, | 732 | DhcpOption::TftpFileName(name) => write_option_len_prefixed_string(&mut writer, &name)?, |
| 397 | DhcpOption::UserClassInformation(user_class) => { | 733 | DhcpOption::UserClassInformation(user_class) => { |
| 398 | write_option_len_prefixed_string(&mut writer, &user_class)? | 734 | write_option_len_prefixed_buf(&mut writer, &user_class)? |
| 735 | } | ||
| 736 | DhcpOption::ClientSystemArchitecture(arch) => { | ||
| 737 | wire::write_u8(&mut writer, 2)?; | ||
| 738 | wire::write_u16(&mut writer, u16::from(*arch))?; | ||
| 399 | } | 739 | } |
| 400 | DhcpOption::ClientMachineIdentifier(identifier) => { | 740 | DhcpOption::ClientMachineIdentifier(identifier) => { |
| 401 | write_option_len_prefixed_buf(&mut writer, &identifier)? | 741 | write_option_len_prefixed_buf(&mut writer, &identifier)? |
