use core::net::Ipv4Addr; use dhcparse::{ dhcpv4::{self, DhcpOption, MessageType as DhcpMsgType}, v4_options, }; use embassy_net::{ IpEndpoint, Stack, udp::{PacketMetadata, UdpMetadata, UdpSocket}, }; use embassy_time::Timer; use heapless::Vec; use log::{info, warn}; use pico_website::unwrap; #[embassy_executor::task(pool_size = 1)] pub async fn dhcp_server(stack: Stack<'static>) { let mut rx_buffer = [0; 4096]; let mut tx_buffer = [0; 4096]; let mut rx_meta = [PacketMetadata::EMPTY; 16]; let mut tx_meta = [PacketMetadata::EMPTY; 16]; let mut buf = [0; 4096]; let mut res_buf = Vec::::new(); let mut opts = Vec::::new(); let mut current_ip = 10_u8; // add one at each new connection, loop at 250, hope not to get on occupied ip 'listen: loop { let mut socket = UdpSocket::new( stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer, ); unwrap(socket.bind(67)).await; info!("Starting DHCP server"); loop { let (n, _) = unwrap(socket.recv_from(&mut buf).await).await; let msg = unwrap(dhcpv4::Message::new(&buf[..n])).await; let msg_type = unwrap(v4_options!(msg; MessageType required)).await; let mut rapid_commit = false; if unwrap(msg.options()) .await .any(|opt| matches!(opt, Ok((DhcpOption::Unknown(80, _), _)))) { if msg_type != DhcpMsgType::DISCOVER { warn!("WARN : dhcp rapid commit option on {:?} message", msg_type); continue 'listen; } rapid_commit = true; } info!("Dhcp: received {:?} message", msg_type); Timer::after_secs(0).await; match msg_type { DhcpMsgType::DISCOVER | DhcpMsgType::REQUEST => { res_buf.clear(); res_buf.push(2).unwrap(); // op res_buf.push(1).unwrap(); // htype res_buf.push(6).unwrap(); // hlen res_buf.push(0).unwrap(); // hops res_buf.extend_from_slice(&buf[4..8]).unwrap(); // xid res_buf.extend_from_slice(&[0; 2]).unwrap(); // secs res_buf.extend_from_slice(&[0x80, 0x00]).unwrap(); // flags res_buf.extend_from_slice(&buf[12..16]).unwrap(); // ciaddr res_buf .extend_from_slice(&[192, 254, 0, current_ip]) .unwrap(); // yiaddr res_buf.extend_from_slice(&[0; 4]).unwrap(); // siaddr res_buf.extend_from_slice(&buf[24..28]).unwrap(); // giaddr res_buf.extend_from_slice(&buf[28..44]).unwrap(); // chaddr res_buf.extend_from_slice(&[0; 192]).unwrap(); // sname/file res_buf.extend_from_slice(&[99, 130, 83, 99]).unwrap(); // magic cookie res_buf .extend_from_slice(&[ 53, 1, match (msg_type, rapid_commit) { (DhcpMsgType::DISCOVER, false) => 2, // DHCP_OFFER _ => 5, // DHCP_ACK }, ]) .unwrap(); // opt message type opts.clear(); opts.extend_from_slice( unwrap(v4_options!(msg; ParameterRequestList)) .await .unwrap_or(&[]), ) .unwrap(); let default_opts: &[u8] = match (msg_type, rapid_commit) { (DhcpMsgType::DISCOVER, false) => &[54], (DhcpMsgType::DISCOVER, true) => &[54, 80], (DhcpMsgType::REQUEST, false) => &[1, 3, 51, 6, 54], _ => unreachable!(), }; for o in default_opts { if !opts.contains(o) { opts.push(*o).unwrap(); } } unwrap(write_dhcp_opts(&mut res_buf, &opts).await).await; res_buf.push(255).unwrap(); // end option unwrap( socket .send_to( &res_buf, UdpMetadata { endpoint: IpEndpoint::new( Ipv4Addr::new(255, 255, 255, 255).into(), 68, ), local_address: Some(Ipv4Addr::new(192, 254, 0, 2).into()), meta: Default::default(), }, ) .await, ) .await; info!("Dhcp: offer/ack sent for ip 192.254.0.{}", current_ip); Timer::after_secs(0).await; if msg_type == DhcpMsgType::REQUEST || rapid_commit { current_ip += 1; if current_ip > 250 { current_ip = 10; } } } _ => {} } // unwrap(socket.send_to(&buf[..n], ep).await).await; } } } async fn write_dhcp_opts(buf: &mut Vec, op_codes: &[u8]) -> Result<(), ()> { for o in op_codes { let (opt_len, opt): (u8, &[u8]) = match o { 1 => (4, &[255, 255, 255, 0]), // DhcpOption::SubnetMask(&dhcpv4::Addr([255, 255, 255, 0])), 2 => (4, &3600_i32.to_be_bytes()), // DhcpOption::TimeOffset(3600), 3 => (4, &[192, 254, 0, 2]), // DhcpOption::Router(&[dhcpv4::Addr([192, 254, 0, 2])]), 6 => (4, &[192, 254, 0, 2]), // DhcpOption::DomainNameServer(&[dhcpv4::Addr([0, 0, 0, 0])]), 12 => (4, b"blue"), // DhcpOption::HostName(b"blue"), 15 => (4, b"wifi"), // DhcpOption::DomainName(b"LocalDomain"), 26 => (2, &1514_u16.to_be_bytes()), // DhcpOption::Unknown(26, &[0x5, 0xEA]), // mtu 28 => (4, &[192, 254, 0, 255]), // DhcpOption::Unknown(28, &[192, 254, 0, 255]), // broadcast 51 => (4, &700_u32.to_be_bytes()), // DhcpOption::AddressLeaseTime(700), 54 => (4, &[192, 254, 0, 2]), // DhcpOption::ServerIdentifier(&dhcpv4::Addr([192, 254, 0, 2])), 58 => (4, &500_u32.to_be_bytes()), // DhcpOption::Unknown(58, &[0, 0, 0x1, 0xF4]), // renewal time = 500s 59 => (4, &600_u32.to_be_bytes()), // DhcpOption::Unknown(59, &[0, 0, 0x2, 0x58]), // rebinding time = 600s 80 => (0, &[]), _ => { info!("Dhcp: unhandled requested option {}", o); Timer::after_secs(0).await; continue; } }; buf.push(*o).map_err(|_| ())?; buf.push(opt_len).map_err(|_| ())?; buf.extend_from_slice(opt)?; } Ok(()) }