aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authordiogo464 <[email protected]>2025-10-08 19:03:56 +0100
committerdiogo464 <[email protected]>2025-10-08 19:03:56 +0100
commit89db26c86bf48a4c527778fc254765a38b7e9085 (patch)
treec3b441f3db8b9e17b652269cb0bd5ac720d2e51a /src/main.rs
parent7a52879e0db0e4fb311ec840938c5fc4e5775afc (diff)
dhcp module split done
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs382
1 files changed, 34 insertions, 348 deletions
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);