aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/dhcp.rs233
-rw-r--r--src/main.rs382
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 @@
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}
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;
3pub mod tftp; 3pub mod tftp;
4pub mod wire; 4pub mod wire;
5 5
6use std::io::{BufRead, Cursor, Read, Result, Write}; 6use std::{
7use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket}; 7 io::Result,
8 net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket},
9};
8 10
9use ipnet::Ipv4Net; 11use ipnet::Ipv4Net;
10 12
11const FLAG_BROADCAST: u16 = 1 << 15; 13use crate::dhcp::{DhcpOption, DhcpPacket};
12 14
13const OPTION_CODE_PAD: u8 = 0; 15const LOCAL_IPV4: Ipv4Addr = Ipv4Addr::new(192, 168, 1, 103);
14const OPTION_CODE_END: u8 = 255; 16const LOCAL_HOSTNAME: &'static str = "Diogos-Air";
15const OPTION_CODE_VENDOR_CLASS_IDENTIFIER: u8 = 60;
16const OPTION_CODE_USER_CLASS_INFORMATION: u8 = 77;
17
18const 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";
22const BOOT_FILE_NAME: &[u8] = b"ipxe.efi";
23const BOOT_FILE_NAME_IPXE: &[u8] = b"test.ipxe";
24
25const LOCAL_IPV4: Ipv4Addr = Ipv4Addr::new(192, 168, 1, 100);
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28enum BootOp {
29 Request,
30 Reply,
31}
32
33impl 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)]
39enum HardwareType {
40 Ethernet,
41}
42
43impl HardwareType {
44 pub const TYPE_ETHER: u8 = 1;
45 pub const LEN_ETHER: u8 = 6;
46}
47
48#[derive(Debug, Clone)]
49enum DhcpOption {
50 Pad,
51 End,
52 VendorClassIdentifier(String),
53 UserClassInformation(String),
54 Unknown { code: u8, data: Vec<u8> },
55}
56
57#[derive(Debug)]
58struct 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
76fn 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
82fn 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
88fn 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
94fn 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
100fn 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
107fn 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
112fn 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
119fn 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
124fn read_ipv4(cursor: &mut Cursor<&[u8]>) -> Result<Ipv4Addr> {
125 Ok(Ipv4Addr::from_octets(read_arr(cursor)?))
126}
127
128fn 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
140fn 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
150fn 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
170fn 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
201fn write_buf(writer: &mut Vec<u8>, buf: &[u8]) -> Result<()> {
202 writer.write_all(buf)
203}
204
205fn write_u8(writer: &mut Vec<u8>, v: u8) -> Result<()> {
206 write_buf(writer, &[v])
207}
208
209fn write_u16(writer: &mut Vec<u8>, v: u16) -> Result<()> {
210 let buf = u16::to_be_bytes(v);
211 write_buf(writer, &buf)
212}
213
214fn write_u32(writer: &mut Vec<u8>, v: u32) -> Result<()> {
215 let buf = u32::to_be_bytes(v);
216 write_buf(writer, &buf)
217}
218
219fn write_ipv4(writer: &mut Vec<u8>, v: Ipv4Addr) -> Result<()> {
220 write_buf(writer, &v.octets())
221}
222
223fn 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
290fn 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)]
353struct InterfaceAddr { 19struct InterfaceAddr {
@@ -433,7 +99,7 @@ fn main() {
433} 99}
434 100
435fn handle_packet(buf: &[u8], socket: &UdpSocket) { 101fn 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
482fn handle_packet_4011(buf: &[u8], socket: &UdpSocket, sender_addr: SocketAddr) { 159fn 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);