Initial commit

This commit is contained in:
Xnoe 2022-09-23 17:48:40 +01:00
commit f04132bfe1
Signed by: xnoe
GPG Key ID: 45AC398F44F0DAFE
4 changed files with 416 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

177
Cargo.lock generated Normal file
View File

@ -0,0 +1,177 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "dhcprs"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5849fa01b515c56a6247af5121fa38bb22988e2c0f7216d93e5623417a57ea"
dependencies = [
"eui48",
]
[[package]]
name = "eui48"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "887418ac5e8d57c2e66e04bdc2fe15f9a5407be20b54a82c86bd0e368b709701"
dependencies = [
"regex",
"rustc-serialize",
]
[[package]]
name = "getrandom"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "nix"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb"
dependencies = [
"autocfg",
"bitflags",
"cfg-if",
"libc",
"memoffset",
"pin-utils",
]
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "rust-dhcpclient"
version = "0.1.0"
dependencies = [
"dhcprs",
"eui48",
"libc",
"nix",
"rand",
]
[[package]]
name = "rustc-serialize"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

13
Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "rust-dhcpclient"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dhcprs = "0.1.2"
nix = "0.25.0"
libc = "0.2.133"
eui48 = "1.1.0"
rand = "0.8.5"

225
src/main.rs Normal file
View File

@ -0,0 +1,225 @@
use nix::sys::socket::*;
use std::net::Ipv4Addr;
use eui48::MacAddress;
use rand::prelude::*;
use dhcprs::dhcp::DHCPOption;
use dhcprs::dhcp::DHCPMessageType;
fn create_dhcp_packet(xid: u32, mac: MacAddress, source_ip: Option<Ipv4Addr>, dest_ip: Option<Ipv4Addr>, options: Vec<dhcprs::dhcp::DHCPOption>) -> dhcprs::udpbuilder::RawUDPPacket {
let options_bytes = dhcprs::dhcp::DHCPOption::to_bytes(options);
let mut vend = [0; 312];
vend[..options_bytes.len()].copy_from_slice(&options_bytes);
let bootppacket = dhcprs::bootp::BOOTPPacket::new(
dhcprs::bootp::OpCode::BOOTREQUEST, // This is a DHCP client, we'll only be dealing with bootrequests
0,
xid,
0,
0,
source_ip,
None,
dest_ip,
None,
mac,
[0; 64],
[0; 128],
vend
);
let udppacket = dhcprs::udpbuilder::UDPPacket::new(
source_ip.unwrap_or(Ipv4Addr::new(0,0,0,0)),
dest_ip.unwrap_or(Ipv4Addr::new(255,255,255,255)),
68,
67,
dhcprs::bootp::RawBOOTPPacket::from(bootppacket).as_bytes().try_into().unwrap()
);
return dhcprs::udpbuilder::RawUDPPacket::from(udppacket);
}
enum DHCPTransactionState {
Discover,
WaitingAfterDiscover,
Request,
WaitAfterRequest,
}
fn dhcp_client(name: String, mac: LinkAddr) {
let mut rng = rand::thread_rng();
// Before we can do anything else, we need to construct an LinkAddr for the mac ff:ff:ff:ff:ff:ff
println!("Starting DHCP on interface {}", name);
let sockaddr_ll = libc::sockaddr_ll {
sll_family: libc::AF_PACKET as u16,
sll_halen: mac.halen() as u8,
sll_hatype: mac.hatype(),
sll_ifindex: mac.ifindex() as i32,
sll_addr: [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0],
sll_pkttype: mac.pkttype(),
sll_protocol: 0x0008
};
let mut linkaddr = unsafe {LinkAddr::from_raw(&sockaddr_ll as *const libc::sockaddr_ll as *const libc::sockaddr, Some(20)).expect("Failed to create linkaddr!")};
// Create and bind the socket
let socket = socket(AddressFamily::Packet, SockType::Datagram, SockFlag::empty(), SockProtocol::Udp).expect("Failed to create socket! Permission issue?");
assert!(setsockopt(socket, sockopt::Broadcast, &true).is_ok());
assert!(bind(socket, &linkaddr).is_ok());
let client_mac = MacAddress::from_bytes(&mac.addr().unwrap()).unwrap();
let mut client_addr: Option<Ipv4Addr> = None;
let mut server_addr: Option<Ipv4Addr> = None;
// DHCP transaction loop
'dhcp_transaction: loop {
let xid: u32 = rng.gen();
let mut dhcp_state = DHCPTransactionState::Discover;
'dhcp_message_loop: loop {
match &dhcp_state {
DHCPTransactionState::Discover => {
println!("Sent DHCPDiscover on {}", name);
let _ = sendto(socket, create_dhcp_packet(xid, client_mac, None, None, vec![
DHCPOption::DHCPMessageType(DHCPMessageType::DHCPDiscover),
DHCPOption::End
]).as_bytes(), &linkaddr, MsgFlags::empty()).unwrap();
dhcp_state = DHCPTransactionState::WaitingAfterDiscover;
}
DHCPTransactionState::WaitingAfterDiscover => {
let mut packet: [u8; 574] = [0; 574];
let (bytes, addr) = recvfrom::<LinkAddr>(socket, &mut packet).unwrap();
println!("Received {} bytes on {}", bytes, name);
let udppacet_raw: dhcprs::udpbuilder::RawUDPPacket = unsafe {std::ptr::read(packet.as_ptr() as *const _)};
let udppacket: dhcprs::udpbuilder::UDPPacket = udppacet_raw.into();
if udppacket.source_port != 67 {
// Not a BOOTP reply, get the next packet.
continue 'dhcp_message_loop;
};
let bootppacket_raw: dhcprs::bootp::RawBOOTPPacket = unsafe {std::ptr::read(udppacket.get_data().as_ptr() as *const _)};
let bootppacket: dhcprs::bootp::BOOTPPacket = bootppacket_raw.into();
if bootppacket.xid != xid {
// Not a reply to us, get the next packet.
continue 'dhcp_message_loop;
};
let options = dhcprs::dhcp::DHCPOption::from_bytes(&bootppacket.get_vend()[4..]);
if !options.iter().find(|&x| if let DHCPOption::DHCPMessageType(DHCPMessageType::DHCPOffer) = x {true} else {false}).is_some() {
// We got a response but it wasn't the expected message, try again with another transaction.
continue 'dhcp_transaction;
}
// Valid DHCPOffer received, process it.
client_addr = bootppacket.yiaddr;
server_addr = bootppacket.siaddr;
println!("Got client address: {}", client_addr.unwrap_or(Ipv4Addr::new(0,0,0,0)));
println!("Got server address: {}", server_addr.unwrap_or(Ipv4Addr::new(0,0,0,0)));
// Update the linkaddr to be the server's actual hardware address rather than the broadcast MAC.
let mut mac: [u8; 8] = [0; 8];
mac[..6].copy_from_slice(&addr.unwrap().addr().unwrap());
let new_sockaddr_ll = libc::sockaddr_ll {
sll_family: sockaddr_ll.sll_family,
sll_halen: sockaddr_ll.sll_halen,
sll_hatype: sockaddr_ll.sll_hatype,
sll_ifindex: sockaddr_ll.sll_ifindex,
sll_addr: mac,
sll_pkttype: sockaddr_ll.sll_pkttype,
sll_protocol: sockaddr_ll.sll_protocol
};
linkaddr = unsafe {LinkAddr::from_raw(&new_sockaddr_ll as *const libc::sockaddr_ll as *const libc::sockaddr, Some(20)).expect("Failed to update linkaddr to server's hardware address!")};
dhcp_state = DHCPTransactionState::Request;
}
DHCPTransactionState::Request => {
println!("Sent DHCPRequest on {}", name);
let _ = sendto(socket, create_dhcp_packet(xid, client_mac, client_addr, server_addr, vec![
DHCPOption::DHCPMessageType(DHCPMessageType::DHCPRequest),
DHCPOption::ParameterRequest(vec![1, 3, 6, 28]),
DHCPOption::End
]).as_bytes(), &linkaddr, MsgFlags::empty());
dhcp_state = DHCPTransactionState::WaitAfterRequest;
}
DHCPTransactionState::WaitAfterRequest => {
let mut packet: [u8; 574] = [0; 574];
let (bytes, _) = recvfrom::<LinkAddr>(socket, &mut packet).unwrap();
println!("Received {} bytes on {}", bytes, name);
let udppacet_raw: dhcprs::udpbuilder::RawUDPPacket = unsafe {std::ptr::read(packet.as_ptr() as *const _)};
let udppacket: dhcprs::udpbuilder::UDPPacket = udppacet_raw.into();
if udppacket.source_port != 67 {
// Not a BOOTP reply, get the next packet.
continue 'dhcp_message_loop;
};
let bootppacket_raw: dhcprs::bootp::RawBOOTPPacket = unsafe {std::ptr::read(udppacket.get_data().as_ptr() as *const _)};
let bootppacket: dhcprs::bootp::BOOTPPacket = bootppacket_raw.into();
if bootppacket.xid != xid {
// Not a reply to us, get the next packet.
continue 'dhcp_message_loop;
};
let options = dhcprs::dhcp::DHCPOption::from_bytes(&bootppacket.get_vend()[4..]);
if !options.iter().find(|&x| if let DHCPOption::DHCPMessageType(DHCPMessageType::DHCPACK) = x {true} else {false}).is_some() {
// Wasn't an ACK, probably NAK, try again.
println!("Did not receive ACK from DHCPRequest, sleeping for 10secs before continuing.");
std::thread::sleep(core::time::Duration::new(10, 0));
continue 'dhcp_transaction;
}
let mut sleep_time = 0;
for option in options {
println!("Received: {:?}", option);
match option {
DHCPOption::IPAddressLeaseTime(n) => {
sleep_time = n
}
_ => ()
}
}
println!("Sleeping for {} for lease time to elapse.", sleep_time);
std::thread::sleep(core::time::Duration::new(sleep_time.into(), 0));
}
_ => ()
}
}
}
}
fn main() {
let threads: Vec<std::thread::JoinHandle<()>> = nix::ifaddrs::getifaddrs().unwrap()
.filter(|x| x.address.is_some())
.filter(|x| x.address.unwrap().as_link_addr().is_some())
.filter(|x| x.address.unwrap().as_link_addr().unwrap().hatype() == 1) // Only perform DHCP on Ethernet interfaces.
.map(|x| (x.interface_name, *x.address.unwrap().as_link_addr().unwrap()))
.map(|(name, mac)| std::thread::spawn(move || { dhcp_client(name, mac)} ))
.collect();
for thread in threads {
let _ = thread.join();
}
}