Files
dhcpv6stateless/src/main.rs

122 lines
3.7 KiB
Rust

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(())
}