use anyhow::{ Context, Result, }; use clap::Parser; use dhcproto::{ Decodable, Decoder, Encodable, Encoder, v6::{ DhcpOption, Message, MessageType, OptionCode, ORO, }, }; use ipnetwork; use log::{ debug, log_enabled, info, Level, }; use pnet; use std::net::SocketAddrV6; use tokio::net::UdpSocket; const ALL_DHCP_RELAY_AGENTS_AND_SERVERS: &str = "ff02::1:2"; const DHCP_CLIENT_PORT: u16 = 546; const DHCP_RELAY_AGENT_AND_SERVER_PORT: u16 = 547; /// Requests some simple parameters using DHCPv6 from specified network #[derive(Parser)] #[command(version, about, long_about = None)] struct Cli { /// Network interface name used for DHCPv6 interface: String, } #[tokio::main] async fn main() -> Result<()>{ env_logger::init(); let cli = Cli::parse(); let all_interfaces = pnet::datalink::interfaces(); let selected_interface = all_interfaces .iter() .find(|i| i.name == cli.interface) .with_context(|| format!("No interface found with name: {}", cli.interface))?; let ipv6_addresses = selected_interface.ips .iter() .filter_map(|a| match a { ipnetwork::IpNetwork::V6(a) => Some(a), _ => None, }); let mut ipv6_link_local_addresses = ipv6_addresses .filter(|a| a.is_subnet_of("fe80::/10".parse().unwrap())); // Just take the first address found on the interface let selected_address = ipv6_link_local_addresses.next() .context("No IPv6 link local address assigned to this interface")?; let socket_addr = SocketAddrV6::new(selected_address.ip(), DHCP_CLIENT_PORT, 0, selected_interface.index.clone()); let sock = UdpSocket::bind(socket_addr).await .context("Unable to bind UDP socket")?; info!("Listening on {}", sock.local_addr() .context("Failed to fetch address socket bound to")?); let remote_addr = SocketAddrV6::new(ALL_DHCP_RELAY_AGENTS_AND_SERVERS.parse().unwrap(), DHCP_RELAY_AGENT_AND_SERVER_PORT, 0, selected_interface.index.clone()); let mut msg = Message::new(MessageType::InformationRequest); let msg_opts = msg.opts_mut(); //msg_opts.insert(DhcpOption::ClientId(Vec::from(duid))); msg_opts.insert(DhcpOption::ElapsedTime(0)); msg_opts.insert(DhcpOption::ORO(ORO { opts: vec![ OptionCode::InfMaxRt, OptionCode::InformationRefreshTime, OptionCode::DomainNameServers, OptionCode::DomainSearchList, ],})); // request options let mut msg_buf = Vec::new(); let mut msg_encoder = Encoder::new(&mut msg_buf); msg.encode(&mut msg_encoder) .context("Unable to serialize DHCP options for message to send")?; // Serializes msg to msg_buf sock.send_to(&msg_buf, remote_addr).await .context("Failed to send DHCPv6 INFORMATION-REQUEST message")?; let response_msg = loop { let mut recv_buf = [0; 1024]; let (recv_len, recv_addr) = sock.recv_from(&mut recv_buf).await .context("Failed to receive DHCPv6 response")?; if log_enabled!(Level::Debug) { debug!("{:?} bytes received from {:?}", recv_len, recv_addr); debug!("Received packet: {}", hex::encode(&recv_buf[..recv_len])); } let recv_msg = Message::decode(&mut Decoder::new(&recv_buf)) .context("Unable to parse DHCPv6 response message")?; if log_enabled!(Level::Debug) { debug!("Packet contains transaction id: 0x{}", hex::encode(&msg.xid())); } if recv_msg.xid() == msg.xid() { info!("Found reponse with matching transaction id"); break recv_msg; } }; println!("{}", &response_msg); Ok(()) }