diff options
Diffstat (limited to 'src/dhcp.rs')
| -rw-r--r-- | src/dhcp.rs | 233 |
1 files changed, 219 insertions, 14 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 | } | ||
