aboutsummaryrefslogtreecommitdiff
path: root/src/dhcp.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/dhcp.rs')
-rw-r--r--src/dhcp.rs214
1 files changed, 214 insertions, 0 deletions
diff --git a/src/dhcp.rs b/src/dhcp.rs
new file mode 100644
index 0000000..b53ee92
--- /dev/null
+++ b/src/dhcp.rs
@@ -0,0 +1,214 @@
1use std::{
2 io::{Result, Write},
3 net::Ipv4Addr,
4};
5
6use crate::wire;
7
8const MAGIC_COOKIE: [u8; 4] = [0x63, 0x82, 0x53, 0x63];
9
10#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum BootOp {
12 #[default]
13 Request,
14 Reply,
15}
16
17impl BootOp {
18 pub const OP_REQUEST: u8 = 1;
19 pub const OP_REPLY: u8 = 2;
20}
21
22impl From<BootOp> for u8 {
23 fn from(value: BootOp) -> Self {
24 match value {
25 BootOp::Request => BootOp::OP_REQUEST,
26 BootOp::Reply => BootOp::OP_REPLY,
27 }
28 }
29}
30
31#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
32pub enum HardwareType {
33 #[default]
34 Ethernet,
35}
36
37impl HardwareType {
38 pub const TYPE_ETHER: u8 = 1;
39 pub const LEN_ETHER: u8 = 6;
40
41 pub fn hardware_len(&self) -> u8 {
42 match self {
43 HardwareType::Ethernet => Self::LEN_ETHER,
44 }
45 }
46}
47
48impl From<HardwareType> for u8 {
49 fn from(value: HardwareType) -> Self {
50 match value {
51 HardwareType::Ethernet => HardwareType::TYPE_ETHER,
52 }
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57pub enum DhcpMessageType {
58 Ack,
59}
60
61impl DhcpMessageType {
62 pub const CODE_ACK: u8 = 5;
63
64 pub fn code(&self) -> u8 {
65 match self {
66 DhcpMessageType::Ack => Self::CODE_ACK,
67 }
68 }
69}
70
71#[derive(Debug, Clone)]
72pub enum DhcpOption {
73 Pad,
74 End,
75 MessageType(DhcpMessageType),
76 ServerIdentifier(Ipv4Addr),
77 VendorClassIdentifier(String),
78 TftpServerName(String),
79 TftpFileName(String),
80 UserClassInformation(String),
81 ClientMachineIdentifier(String),
82 Unknown { code: u8, data: Vec<u8> },
83}
84
85impl DhcpOption {
86 pub const CODE_PAD: u8 = 0;
87 pub const CODE_END: u8 = 255;
88 pub const CODE_DHCP_MESSAGE_TYPE: u8 = 53;
89 pub const CODE_DHCP_SERVER_IDENTIFIER: u8 = 54;
90 pub const CODE_VENDOR_CLASS_IDENTIFIER: u8 = 60;
91 pub const CODE_TFTP_SERVER_NAME: u8 = 66;
92 pub const CODE_TFTP_FILE_NAME: u8 = 67;
93 pub const CODE_USER_CLASS_INFORMATION: u8 = 77;
94 pub const CODE_CLIENT_MACHINE_IDENTIFIER: u8 = 97;
95
96 pub fn code(&self) -> u8 {
97 match self {
98 DhcpOption::Pad => Self::CODE_PAD,
99 DhcpOption::End => Self::CODE_END,
100 DhcpOption::MessageType(_) => Self::CODE_DHCP_MESSAGE_TYPE,
101 DhcpOption::ServerIdentifier(_) => Self::CODE_DHCP_SERVER_IDENTIFIER,
102 DhcpOption::VendorClassIdentifier(_) => Self::CODE_VENDOR_CLASS_IDENTIFIER,
103 DhcpOption::TftpServerName(_) => Self::CODE_TFTP_SERVER_NAME,
104 DhcpOption::TftpFileName(_) => Self::CODE_TFTP_FILE_NAME,
105 DhcpOption::UserClassInformation(_) => Self::CODE_USER_CLASS_INFORMATION,
106 DhcpOption::ClientMachineIdentifier(_) => Self::CODE_CLIENT_MACHINE_IDENTIFIER,
107 DhcpOption::Unknown { code, .. } => *code,
108 }
109 }
110}
111
112#[derive(Debug)]
113pub struct DhcpPacket {
114 pub op: BootOp,
115 pub htype: HardwareType,
116 pub xid: u32,
117 pub secs: u16,
118 pub flags: u16,
119 pub ciaddr: Ipv4Addr,
120 pub yiaddr: Ipv4Addr,
121 pub siaddr: Ipv4Addr,
122 pub giaddr: Ipv4Addr,
123 pub chaddr: [u8; 16],
124 // server host name
125 pub sname: Option<String>,
126 // boot file name
127 pub file: Option<String>,
128 pub options: Vec<DhcpOption>,
129}
130
131impl Default for DhcpPacket {
132 fn default() -> Self {
133 Self {
134 op: Default::default(),
135 htype: Default::default(),
136 xid: Default::default(),
137 secs: Default::default(),
138 flags: Default::default(),
139 ciaddr: Ipv4Addr::UNSPECIFIED,
140 yiaddr: Ipv4Addr::UNSPECIFIED,
141 siaddr: Ipv4Addr::UNSPECIFIED,
142 giaddr: Ipv4Addr::UNSPECIFIED,
143 chaddr: Default::default(),
144 sname: Default::default(),
145 file: Default::default(),
146 options: Default::default(),
147 }
148 }
149}
150
151pub fn write_packet<W: Write>(mut writer: W, packet: &DhcpPacket) -> Result<()> {
152 wire::write_u8(&mut writer, u8::from(packet.op))?;
153 wire::write_u8(&mut writer, u8::from(packet.htype))?;
154 wire::write_u8(&mut writer, packet.htype.hardware_len())?;
155 wire::write_u8(&mut writer, 0)?; // hops
156 wire::write_u32(&mut writer, packet.xid)?;
157 wire::write_u16(&mut writer, packet.secs)?;
158 wire::write_u16(&mut writer, packet.flags)?;
159 wire::write_ipv4(&mut writer, packet.ciaddr)?;
160 wire::write_ipv4(&mut writer, packet.yiaddr)?;
161 wire::write_ipv4(&mut writer, packet.siaddr)?;
162 wire::write_ipv4(&mut writer, packet.giaddr)?;
163 wire::write(&mut writer, &packet.chaddr)?;
164 match &packet.sname {
165 Some(name) => wire::write_null_terminated_string(&mut writer, &name)?,
166 None => wire::write_null_terminated_string(&mut writer, "")?,
167 };
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)?;
173 for option in &packet.options {
174 write_option(&mut writer, option)?;
175 }
176 write_option(&mut writer, &DhcpOption::End)?;
177 Ok(())
178}
179
180pub fn write_option<W: Write>(mut writer: W, option: &DhcpOption) -> Result<()> {
181 wire::write_u8(&mut writer, option.code())?;
182 match option {
183 DhcpOption::Pad | DhcpOption::End => {}
184 DhcpOption::MessageType(t) => {
185 wire::write_u8(&mut writer, 1)?;
186 wire::write_u8(&mut writer, t.code())?;
187 }
188 DhcpOption::ServerIdentifier(ip) => {
189 wire::write_u8(&mut writer, 4)?;
190 wire::write_ipv4(&mut writer, *ip)?;
191 }
192 DhcpOption::VendorClassIdentifier(vendor_class) => {
193 write_option_len_prefixed_string(&mut writer, &vendor_class)?
194 }
195 DhcpOption::TftpServerName(name) => write_option_len_prefixed_string(&mut writer, &name)?,
196 DhcpOption::TftpFileName(name) => write_option_len_prefixed_string(&mut writer, &name)?,
197 DhcpOption::UserClassInformation(user_class) => {
198 write_option_len_prefixed_string(&mut writer, &user_class)?
199 }
200 DhcpOption::ClientMachineIdentifier(identifier) => {
201 write_option_len_prefixed_string(&mut writer, &identifier)?
202 }
203 DhcpOption::Unknown { data, .. } => {
204 wire::write_u8(&mut writer, u8::try_from(data.len()).unwrap())?;
205 wire::write(&mut writer, &data)?;
206 }
207 }
208 Ok(())
209}
210
211fn 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())?;
213 wire::write(&mut writer, s.as_bytes())
214}