|
| 1 | +// Dynamic Host Configuration Protocol |
| 2 | + |
| 3 | +use std::{error::Error, net::UdpSocket}; |
| 4 | + |
| 5 | +use byteorder::{ByteOrder, NetworkEndian}; |
| 6 | +use simple_endian::BigEndian; |
| 7 | + |
| 8 | +/// Size of IPv4 adderess in octets. |
| 9 | +/// |
| 10 | +/// [RFC 8200 § 2]: https://www.rfc-editor.org/rfc/rfc791#section-3.2 |
| 11 | +pub const ADDR_SIZE: usize = 4; |
| 12 | + |
| 13 | +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] |
| 14 | +pub struct Ipv4Addr(pub [u8; ADDR_SIZE]); |
| 15 | + |
| 16 | +impl Ipv4Addr { |
| 17 | + const EMPTY: Self = Self([0; ADDR_SIZE]); |
| 18 | +} |
| 19 | + |
| 20 | +// FIXME: The MAC address is usually obtained by using getifaddrs() which currently |
| 21 | +// is unimplemented in mlibc. |
| 22 | +const MAC_ADDRESS: &[u8] = &[52, 54, 0, 12, 34, 56]; |
| 23 | +const DHCP_XID: u32 = 0x43424140; |
| 24 | + |
| 25 | +#[repr(u8)] |
| 26 | +enum DhcpType { |
| 27 | + BootRequest = 1u8.swap_bytes(), |
| 28 | + // BootReply = 2u8.swap_bytes(), |
| 29 | +} |
| 30 | + |
| 31 | +#[repr(u8)] |
| 32 | +enum HType { |
| 33 | + Ethernet = 1u8.swap_bytes(), |
| 34 | +} |
| 35 | + |
| 36 | +#[repr(C, packed)] |
| 37 | +struct Header { |
| 38 | + op: DhcpType, |
| 39 | + htype: HType, |
| 40 | + hlen: BigEndian<u8>, |
| 41 | + hops: BigEndian<u8>, |
| 42 | + xid: BigEndian<u32>, |
| 43 | + seconds: BigEndian<u16>, |
| 44 | + flags: BigEndian<u16>, |
| 45 | + client_ip: Ipv4Addr, |
| 46 | + your_ip: Ipv4Addr, |
| 47 | + server_ip: Ipv4Addr, |
| 48 | + gateway_ip: Ipv4Addr, |
| 49 | + client_hw_addr: [u8; 16], |
| 50 | + server_name: [u8; 64], |
| 51 | + file: [u8; 128], |
| 52 | + options: [u8; 64], |
| 53 | +} |
| 54 | + |
| 55 | +impl Header { |
| 56 | + fn options_mut(&mut self) -> OptionsWriter<'_> { |
| 57 | + OptionsWriter::new(&mut self.options) |
| 58 | + } |
| 59 | +} |
| 60 | + |
| 61 | +#[repr(u8)] |
| 62 | +enum MessageType { |
| 63 | + Discover = 1u8.swap_bytes(), |
| 64 | +} |
| 65 | + |
| 66 | +#[repr(u8)] |
| 67 | +enum DhcpOption { |
| 68 | + HostName = 12, |
| 69 | + MessageType = 53, |
| 70 | + ParameterRequestList = 55, |
| 71 | + ClientIdentifier = 61, |
| 72 | + End = 255, |
| 73 | +} |
| 74 | + |
| 75 | +struct OptionsWriter<'a>(&'a mut [u8]); |
| 76 | + |
| 77 | +impl<'a> OptionsWriter<'a> { |
| 78 | + fn new(options: &'a mut [u8]) -> Self { |
| 79 | + options.fill(0); |
| 80 | + Self(options).set_magic_cookie() |
| 81 | + } |
| 82 | + |
| 83 | + fn insert(&mut self, kind: DhcpOption, data: &'_ [u8]) { |
| 84 | + let total_len = 2 + data.len(); |
| 85 | + |
| 86 | + assert!(data.len() < u8::MAX as _); |
| 87 | + assert!(self.0.len() > total_len); |
| 88 | + |
| 89 | + let (buf, rest) = core::mem::take(&mut self.0).split_at_mut(total_len); |
| 90 | + self.0 = rest; |
| 91 | + |
| 92 | + buf[0] = kind as u8; |
| 93 | + buf[1] = data.len() as _; |
| 94 | + buf[2..].copy_from_slice(data); |
| 95 | + } |
| 96 | + |
| 97 | + fn insert_padding(&mut self, size: usize) { |
| 98 | + let (buf, rest) = core::mem::take(&mut self.0).split_at_mut(size); |
| 99 | + self.0 = rest; |
| 100 | + |
| 101 | + buf.fill(0); |
| 102 | + } |
| 103 | + |
| 104 | + fn set_magic_cookie(mut self) -> Self { |
| 105 | + let (buf, rest) = core::mem::take(&mut self.0).split_at_mut(core::mem::size_of::<u32>()); |
| 106 | + |
| 107 | + // The first four octets of the 'options' field of the DHCP message |
| 108 | + // contain the (decimal) values 99, 130, 83 and 99, respectively. |
| 109 | + // |
| 110 | + // CC: (https://www.rfc-editor.org/rfc/rfc2131#section-3) |
| 111 | + NetworkEndian::write_u32(buf, 0x63825363); |
| 112 | + self.0 = rest; |
| 113 | + self |
| 114 | + } |
| 115 | + |
| 116 | + fn set_message_type(mut self, typ: MessageType) -> Self { |
| 117 | + self.insert(DhcpOption::MessageType, &[typ as u8]); |
| 118 | + self |
| 119 | + } |
| 120 | + |
| 121 | + fn set_parameter_request_list(mut self) -> Self { |
| 122 | + // TODO: Take all of the request flags as an argument. |
| 123 | + self.insert( |
| 124 | + DhcpOption::ParameterRequestList, |
| 125 | + &[ |
| 126 | + 1, // Subnet Mask |
| 127 | + 3, // Router |
| 128 | + 15, // Domain Name |
| 129 | + 6, // Domain Server |
| 130 | + ], |
| 131 | + ); |
| 132 | + self |
| 133 | + } |
| 134 | + |
| 135 | + fn set_client_identifier(mut self) -> Self { |
| 136 | + let mut data = [0; 7]; |
| 137 | + data[0] = HType::Ethernet as u8; |
| 138 | + data[1..].copy_from_slice(MAC_ADDRESS); |
| 139 | + |
| 140 | + self.insert(DhcpOption::ClientIdentifier, data.as_slice()); |
| 141 | + self |
| 142 | + } |
| 143 | + |
| 144 | + fn set_host_name(mut self, name: &str) -> Self { |
| 145 | + self.insert(DhcpOption::HostName, name.as_bytes()); |
| 146 | + self.insert_padding(1); // null-terminator |
| 147 | + self |
| 148 | + } |
| 149 | +} |
| 150 | + |
| 151 | +impl<'a> Drop for OptionsWriter<'a> { |
| 152 | + fn drop(&mut self) { |
| 153 | + self.insert(DhcpOption::End, &[]); |
| 154 | + } |
| 155 | +} |
| 156 | + |
| 157 | +pub fn main() -> Result<(), Box<dyn Error>> { |
| 158 | + let socket = UdpSocket::bind(("0.0.0.0", 68))?; |
| 159 | + socket.connect(("255.255.255.255", 67))?; |
| 160 | + |
| 161 | + let mut client_hw_addr = [0; 16]; |
| 162 | + client_hw_addr[0..6].copy_from_slice(MAC_ADDRESS); |
| 163 | + |
| 164 | + let mut header = Header { |
| 165 | + htype: HType::Ethernet, |
| 166 | + hlen: BigEndian::<u8>::from(6), |
| 167 | + hops: BigEndian::<u8>::from(0), |
| 168 | + xid: BigEndian::<u32>::from(DHCP_XID), |
| 169 | + seconds: BigEndian::<u16>::from(0), |
| 170 | + client_hw_addr, |
| 171 | + server_name: [0; 64], |
| 172 | + file: [0; 128], |
| 173 | + options: [0; 64], |
| 174 | + |
| 175 | + // request info: |
| 176 | + op: DhcpType::BootRequest, |
| 177 | + flags: BigEndian::from(0x8000), // broadcast |
| 178 | + client_ip: Ipv4Addr::EMPTY, |
| 179 | + your_ip: Ipv4Addr::EMPTY, |
| 180 | + server_ip: Ipv4Addr::EMPTY, |
| 181 | + gateway_ip: Ipv4Addr::EMPTY, |
| 182 | + }; |
| 183 | + |
| 184 | + let _ = header |
| 185 | + .options_mut() |
| 186 | + .set_message_type(MessageType::Discover) |
| 187 | + .set_client_identifier() |
| 188 | + .set_host_name("Aero") |
| 189 | + .set_parameter_request_list(); |
| 190 | + |
| 191 | + let header_bytes = unsafe { |
| 192 | + core::slice::from_raw_parts( |
| 193 | + (&header as *const Header) as *const u8, |
| 194 | + std::mem::size_of::<Header>(), |
| 195 | + ) |
| 196 | + }; |
| 197 | + |
| 198 | + socket.send(&header_bytes)?; |
| 199 | + Ok(()) |
| 200 | +} |
0 commit comments