commit f04132bfe1e6f23888275623da879911eec93b72 Author: Xnoe Date: Fri Sep 23 17:48:40 2022 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f6398b3 --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1fe5efd --- /dev/null +++ b/Cargo.toml @@ -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" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..36bb551 --- /dev/null +++ b/src/main.rs @@ -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, dest_ip: Option, options: Vec) -> 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 = None; + let mut server_addr: Option = 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::(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::(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> = 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(); + } +} \ No newline at end of file