122 lines
3.7 KiB
Rust
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(())
|
|
}
|