1use anyhow::Context;
2use dhcproto::v4::{
3 Decodable, Decoder, DhcpOption, Encodable, Encoder, Message, MessageType, Opcode, OptionCode,
4};
5use jiff::{ToSpan, Unit, Zoned};
6use rand::Rng;
7use serde::Serialize;
8use sqlx::sqlite::{SqliteConnectOptions, SqlitePool};
9use std::net::Ipv4Addr;
10use std::str::FromStr;
11use tokio::net::UdpSocket;
12use tracing::{error, info, warn};
13mod db;
14pub mod info;
15
16#[allow(dead_code)]
17#[derive(Debug)]
18struct Lease {
19 ip: i64,
20 client_id: Vec<u8>,
21 leased: bool,
22 expires_at: i64,
23 network: i64,
24 probation: bool,
25}
26
27#[derive(Debug, Serialize)]
28pub struct Client {
29 pub ip: Ipv4Addr,
30 pub client_id: String,
31}
32
33async fn insert_lease(pool: &SqlitePool, ip: Ipv4Addr, client_id: &Vec<u8>) -> anyhow::Result<()> {
34 let ip = u32::from(ip);
35
36 let expire_at = Zoned::now()
37 .round(Unit::Second)?
38 .checked_add(1.hour())
39 .with_context(|| "Fuck".to_string())?
40 .timestamp()
41 .as_second();
42
43 sqlx::query_file!(
44 "./db/queries/insert-new-lease.sql",
45 ip,
46 client_id,
47 expire_at,
48 0
49 )
50 .execute(pool)
51 .await?;
52 Ok(())
53}
54
55async fn build_dhcp_offer_packet(
56 leases: &SqlitePool,
57 discover_message: &Message,
58) -> anyhow::Result<Message> {
59 let client_id = discover_message.chaddr().to_vec();
60
61 let mut suggested_address = match db::get_ip_from_client_id(leases, &client_id).await {
70 Ok(address) => {
71 info!(
72 "[OFFER] Client {:?} already has IP assigned: {:?}",
73 client_id, address
74 );
75 Some(address)
76 }
77 _ => {
78 warn!(
79 "[OFFER] Client {:?} has no IP assigned in the database",
80 client_id
81 );
82 None
83 }
84 };
85
86 if suggested_address.is_none() {
89 let requested_ip_address = discover_message.opts().get(OptionCode::RequestedIpAddress);
90 info!(
91 "[OFFER] Client requested IP address: {:?}",
92 requested_ip_address
93 );
94 match requested_ip_address {
95 Some(DhcpOption::RequestedIpAddress(ip)) => {
96 if !db::is_ip_assigned(leases, *ip).await? {
97 suggested_address = Some(*ip);
98 }
99 }
100 _ => {
101 warn!("[OFFER] No requested IP address")
102 }
103 };
104 }
105
106 if suggested_address.is_none() {
107 let mut max_tries: u8 = 10;
108 loop {
109 let random_address = Ipv4Addr::new(192, 168, 1, rand::thread_rng().gen_range(100..200));
110
111 if !db::is_ip_assigned(leases, random_address).await? {
112 suggested_address = Some(random_address);
113 insert_lease(leases, random_address, &client_id).await?;
115 break;
116 }
117
118 if max_tries == 0 {
119 return Err(anyhow::anyhow!("Could not assign IP address"));
120 } else {
121 max_tries = max_tries.saturating_sub(1);
122 }
123 }
124 }
125
126 let suggested_address = match suggested_address {
127 Some(address) => address,
128 None => return Err(anyhow::anyhow!("Could not assign IP address")),
129 };
130
131 info!("[OFFER] creating offer with IP {}", suggested_address);
132
133 let mut offer = Message::default();
134
135 let reply_opcode = Opcode::BootReply;
136 offer.set_opcode(reply_opcode);
137 offer.set_xid(discover_message.xid());
138 offer.set_yiaddr(suggested_address);
139 offer.set_siaddr(Ipv4Addr::new(192, 168, 1, 69));
140 offer.set_flags(discover_message.flags());
141 offer.set_giaddr(discover_message.giaddr());
142 offer.set_chaddr(discover_message.chaddr());
143
144 offer
145 .opts_mut()
146 .insert(DhcpOption::MessageType(MessageType::Offer));
147 offer.opts_mut().insert(DhcpOption::AddressLeaseTime(3600));
148 offer
149 .opts_mut()
150 .insert(DhcpOption::ServerIdentifier(Ipv4Addr::new(192, 168, 1, 69)));
151 offer
152 .opts_mut()
153 .insert(DhcpOption::SubnetMask(Ipv4Addr::new(255, 255, 255, 0)));
154 offer
155 .opts_mut()
156 .insert(DhcpOption::BroadcastAddr(Ipv4Addr::new(255, 255, 255, 255)));
157 offer
158 .opts_mut()
159 .insert(DhcpOption::Router(vec![Ipv4Addr::new(192, 168, 1, 69)]));
160
161 Ok(offer)
162}
163
164async fn build_dhcp_ack_packet(
165 leases: &SqlitePool,
166 request_message: &Message,
167) -> anyhow::Result<Message> {
168 let server_identifier_option = request_message.opts().get(OptionCode::ServerIdentifier);
169
170 let requested_ip_option = request_message.opts().get(OptionCode::RequestedIpAddress);
171
172 let ciaddr = request_message.ciaddr();
174 let chaddr = request_message.chaddr().to_owned();
175
176 let (is_selecting, is_init_reboot, is_renewing_rebinding) = {
177 let have_server_id = server_identifier_option.is_some();
178 let have_requested_ip = requested_ip_option.is_some();
179 let ciaddr_is_zero = ciaddr == Ipv4Addr::new(0, 0, 0, 0);
180
181 let selecting = have_server_id && have_requested_ip && ciaddr_is_zero;
183
184 let init_reboot = !have_server_id && have_requested_ip && ciaddr_is_zero;
186
187 let renewing_rebinding = ciaddr != Ipv4Addr::new(0, 0, 0, 0) && !have_requested_ip;
189
190 (selecting, init_reboot, renewing_rebinding)
191 };
192
193 let ip_to_validate = if is_selecting || is_init_reboot {
194 match requested_ip_option {
195 Some(DhcpOption::RequestedIpAddress(ip)) => {
196 info!("[ACK] Client requested IP address: {:?}", ip);
197 ip
198 }
199 _ => {
200 anyhow::bail!("[ACK] Client didnt requested IP address")
201 }
202 }
203 } else if is_renewing_rebinding {
204 info!("[ACK] using ciaddr {:?}", ciaddr);
205 &ciaddr
206 } else {
207 anyhow::bail!("[ACK] DHCPREQUEST does not match any known valid state.");
208 };
209
210 let lease = match db::get_lease_by_ip(leases, ip_to_validate).await {
213 Ok(lease) => lease,
214 Err(e) => {
215 anyhow::bail!("[ACK] NO RECORD FOUND ON DB {:?}", e);
216 }
217 };
218
219 if !lease.leased {
220 anyhow::bail!("[ACK] IP address is not leased");
221 }
222
223 let mut ack = Message::default();
224 ack.set_opcode(Opcode::BootReply);
225 ack.set_xid(request_message.xid());
226 ack.set_flags(request_message.flags());
227 ack.set_giaddr(request_message.giaddr());
228 ack.set_chaddr(&chaddr);
229
230 ack.set_yiaddr(*ip_to_validate);
231
232 ack.opts_mut()
233 .insert(DhcpOption::MessageType(MessageType::Ack));
234 ack.opts_mut()
235 .insert(DhcpOption::ServerIdentifier(Ipv4Addr::new(192, 168, 1, 69)));
236
237 ack.opts_mut().insert(DhcpOption::AddressLeaseTime(3600));
238 ack.opts_mut()
239 .insert(DhcpOption::SubnetMask(Ipv4Addr::new(255, 255, 255, 0)));
240 ack.opts_mut()
241 .insert(DhcpOption::BroadcastAddr(Ipv4Addr::new(255, 255, 255, 255)));
242 ack.opts_mut()
243 .insert(DhcpOption::Router(vec![Ipv4Addr::new(192, 168, 1, 69)]));
244
245 Ok(ack)
246}
247
248#[derive(Clone)]
249pub struct MiniDHCPConfiguration {
250 interface: String,
251 leases: SqlitePool,
252}
253
254impl MiniDHCPConfiguration {
255 pub async fn new(interface: String) -> anyhow::Result<Self> {
256 let conn = SqliteConnectOptions::from_str("sqlite://dhcp.db")?.create_if_missing(true);
257
258 let leases = SqlitePool::connect_with(conn).await?;
259
260 sqlx::migrate!("./db/migrations").run(&leases).await?;
261
262 Ok(Self { leases, interface })
263 }
264}
265
266async fn handle_discover(
267 config: &MiniDHCPConfiguration,
268 decoded_message: &Message,
269) -> anyhow::Result<Vec<u8>> {
270 let transaction_id = decoded_message.xid();
271 let client_address = decoded_message.chaddr();
272 info!("[{:X}] DISCOVER {:?}", transaction_id, client_address);
273 let offer = build_dhcp_offer_packet(&config.leases, decoded_message);
274
275 match offer.await {
276 Ok(offer) => {
277 let offered_ip = offer.yiaddr();
278 info!(
279 "[{:X}] [OFFER]: client {:?} ip {:?}",
280 transaction_id, client_address, offered_ip
281 );
282
283 let mut buf = Vec::new();
284 let mut e = Encoder::new(&mut buf);
285 offer.encode(&mut e)?;
286 Ok(buf)
287 }
288 Err(e) => {
289 anyhow::bail!("OFFER Error: {:?}", e)
290 }
291 }
292}
293
294async fn handle_request(
295 config: &MiniDHCPConfiguration,
296 decoded_message: &Message,
297) -> anyhow::Result<Vec<u8>> {
298 let options = decoded_message.opts();
299 let transaction_id = decoded_message.xid();
300 let client_address = decoded_message.chaddr();
301 let server_identifier = options.get(OptionCode::ServerIdentifier);
302 info!(
303 "[{:X}] REQUEST from {:?} to {:?}",
304 transaction_id, client_address, server_identifier
305 );
306
307 let ack = build_dhcp_ack_packet(&config.leases, decoded_message);
308
309 match ack.await {
310 Ok(ack) => {
311 let mut buf = Vec::new();
312 let mut e = Encoder::new(&mut buf);
313 ack.encode(&mut e)?;
314 let offered_ip = ack.yiaddr();
315 info!(
316 "[{:X}] [ACK]: {:?} {:?}",
317 transaction_id, client_address, offered_ip
318 );
319 Ok(buf)
320 }
321 Err(e) => {
322 anyhow::bail!("ACK Error: {:?}", e)
323 }
324 }
325}
326
327pub async fn start(config: MiniDHCPConfiguration) -> anyhow::Result<()> {
328 let address = "0.0.0.0:67";
329 info!("Starting DHCP listener [{}] {}", config.interface, address);
330 let socket = UdpSocket::bind(address).await?;
331 socket.set_broadcast(true)?;
332 socket.bind_device(Some(config.interface.as_bytes()))?;
333
334 let mut read_buffer = vec![0u8; 1024];
335
336 loop {
337 let (_len, addr) = socket.recv_from(&mut read_buffer).await?;
339 info!("== Received packet from {:?} ==", addr);
340
341 let decoded_message = Message::decode(&mut Decoder::new(&read_buffer))?;
342 if decoded_message.opcode() != Opcode::BootRequest {
345 error!("[ERROR] opcode is not BootRequest, ignoring message");
346 continue;
347 }
348
349 let options = decoded_message.opts();
350
351 if options.has_msg_type(MessageType::Discover) {
352 let transaction_id = decoded_message.xid();
353 let response = handle_discover(&config, &decoded_message).await;
354 if let Ok(response) = response {
355 info!("[{:X}] [OFFER] Sending...", transaction_id);
356 socket
357 .send_to(&response, "255.255.255.255:68")
358 .await
359 .expect("[OFFER] Failed to send in socket");
360 } else {
361 error!("[ERROR] handling DISCOVER {:?}", response);
362 }
363 continue;
364 }
365
366 if options.has_msg_type(MessageType::Request) {
367 let transaction_id = decoded_message.xid();
368 let response = handle_request(&config, &decoded_message).await;
369 if let Ok(response) = response {
370 info!("[{:X}] [ACK] Sending...", transaction_id);
371 socket
372 .send_to(&response, "255.255.255.255:68")
373 .await
374 .expect("[ACK] Failed to send in socket");
375 } else {
376 error!("[ERROR] handling REQUEST {:?}", response);
377 }
378 continue;
379 }
380
381 if options.has_msg_type(MessageType::Decline) {
382 let transaction_id = decoded_message.xid();
383 info!("[{:X}] [DECLINE]", transaction_id);
384 continue;
385 }
386
387 if options.has_msg_type(MessageType::Release) {
388 let transaction_id = decoded_message.xid();
389 info!("[{:X}] [RELEASE]", transaction_id);
390 continue;
391 }
392 if options.has_msg_type(MessageType::Inform) {
393 let transaction_id = decoded_message.xid();
394 info!("[{:X}] [INFORM]", transaction_id);
395 continue;
396 }
397 }
398}