aboutsummaryrefslogtreecommitdiff
path: root/src/dhcp.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/dhcp.rs')
-rw-r--r--src/dhcp.rs233
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 @@
1use std::{ 1use std::{
2 io::{Result, Write}, 2 io::{Cursor, Read as _, Result, Write},
3 net::Ipv4Addr, 3 net::Ipv4Addr,
4}; 4};
5 5
6use crate::wire; 6use crate::wire;
7 7
8const MAGIC_COOKIE: [u8; 4] = [0x63, 0x82, 0x53, 0x63]; 8const MAGIC_COOKIE: [u8; 4] = [0x63, 0x82, 0x53, 0x63];
9const 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)]
11pub enum BootOp { 12pub 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)]
57pub enum DhcpMessageType { 58pub enum DhcpMessageType {
59 Offer,
58 Ack, 60 Ack,
59} 61}
60 62
61impl DhcpMessageType { 63impl 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 {
113pub struct DhcpPacket { 117pub 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
157impl 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
229fn 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
235fn 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
241fn 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
247fn 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
253fn 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
260fn 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
265fn read_ipv4(cursor: &mut Cursor<&[u8]>) -> Result<Ipv4Addr> {
266 Ok(Ipv4Addr::from_octets(read_arr(cursor)?))
267}
268
269fn 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
281fn 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
293fn 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
313fn 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
319fn 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
325pub 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
151pub fn write_packet<W: Write>(mut writer: W, packet: &DhcpPacket) -> Result<()> { 355pub 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
416fn 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}