toe_beans/v4/client/mod.rs
1#![allow(missing_debug_implementations)]
2
3mod config;
4
5pub use config::*;
6
7use crate::v4::message::*;
8use mac_address::get_mac_address;
9use rand::RngCore;
10use rand::rngs::ThreadRng;
11use std::net::Ipv4Addr;
12
13/// The standard port for dhcpv4 clients defined in the RFC.
14pub const CLIENT_PORT: u16 = 68;
15/// This is an arbitrary port number above 1024 that can be used when testing the client.
16/// Port numbers below 1024 are privileged ports that require admin access to bind to.
17/// See: <https://www.w3.org/Daemon/User/Installation/PrivilegedPorts.html>
18pub const TEST_CLIENT_PORT: u16 = 8068;
19/// Resolves to TEST_PORT for debug builds and PORT for release builds
20pub const RESOLVED_CLIENT_PORT: u16 = if cfg!(debug_assertions) {
21 TEST_CLIENT_PORT
22} else {
23 CLIENT_PORT
24};
25
26fn get_chaddr() -> [u8; 16] {
27 let address = get_mac_address().unwrap().unwrap().bytes();
28 let mut chaddr: [u8; 16] = [0; 16];
29 chaddr[..6].copy_from_slice(&address[..6]);
30 chaddr
31}
32
33/// Logic for a DHCP client using this lib.
34///
35/// Used by the client binary and integration tests.
36///
37/// Requires the `v4_client` feature, which is enabled by default.
38pub struct Client {
39 /// A UdpSocket that understands how to use `Message` to communicate with the Server
40 pub socket: Socket,
41 /// The client's hardware address.
42 ///
43 /// This is cached for use in multiple messages to the server,
44 /// and the server will reuse it in its replies.
45 pub haddr: [u8; 16],
46 /// The random number generator used to generate the number for xid.
47 ///
48 /// Defaults to `ThreadRng`, but can be set to a custom value
49 /// with `Client.set_rng()`.
50 rng: Box<dyn RngCore>,
51 // TODO store current lease here
52}
53
54impl Client {
55 /// A wrapper around [UdpSocket::bind](https://doc.rust-lang.org/std/net/struct.UdpSocket.html#method.bind)
56 pub fn new(config: ClientConfig) -> Self {
57 Self {
58 socket: Socket::new(config.socket_config),
59 haddr: get_chaddr(),
60 rng: Box::new(ThreadRng::default()),
61 }
62 }
63
64 /// Change the default random number generator from [ThreadRng](https://docs.rs/rand/latest/rand/rngs/struct.ThreadRng.html) to any rng that implements RngCore from the [rand](https://docs.rs/rand/latest/rand/rngs/index.html) crate.
65 ///
66 /// For example, you could use [SmallRng](https://docs.rs/rand/latest/rand/rngs/struct.SmallRng.html)
67 /// which is sometimes faster, but has a higher collision rate of 1 in 65,000:
68 /// ```
69 /// use toe_beans::v4::{Client, ClientConfig};
70 /// use std::net::{Ipv4Addr, SocketAddrV4};
71 /// use rand::SeedableRng;
72 /// use rand::rngs::SmallRng;
73 ///
74 /// let client_config = ClientConfig::default();
75 /// let mut client = Client::new(client_config);
76 ///
77 /// // initialize the random number generator once.
78 /// let rng = SmallRng::from_os_rng();
79 ///
80 /// client.set_rng(Box::new(rng));
81 /// ```
82 pub fn set_rng(&mut self, rng: Box<dyn RngCore>) {
83 self.rng = rng;
84 }
85
86 /// Generates a number for xid by using the cached, initialized rng.
87 /// Note that generating a number mutates the internal state of the rng.
88 fn get_xid(&mut self) -> u32 {
89 self.rng.next_u32()
90 }
91
92 /// Sent from the client to server as the start of a DHCP conversation.
93 pub fn discover(&mut self) -> Message {
94 Message {
95 op: Ops::Request,
96 htype: HTypes::Ethernet,
97 hlen: 6,
98 hops: 0,
99 xid: self.get_xid(),
100 secs: 0,
101 flags: Flags { broadcast: false }, // TODO or true if required
102 ciaddr: Ipv4Addr::UNSPECIFIED,
103 yiaddr: Ipv4Addr::UNSPECIFIED,
104 siaddr: Ipv4Addr::UNSPECIFIED,
105 giaddr: Ipv4Addr::UNSPECIFIED,
106 chaddr: self.haddr,
107 sname: SName::EMPTY,
108 file: File::EMPTY,
109 magic: MAGIC,
110 options: vec![
111 MessageOptions::MessageType(MessageTypes::Discover),
112 // TODO may set option 50 to request an ip address
113 ]
114 .into(),
115 }
116 }
117
118 /// Sent from client to server (during RENEWING/REBINDING) when the client has a lease but wants to renew or rebind it.
119 ///
120 /// A renew will unicast, whereas a rebind will broadcast.
121 /// Rebinding is intended for multiple DHCP servers that can coordinate consistenency of their leases
122 pub fn extend(&mut self, address: Ipv4Addr) -> Message {
123 Message {
124 op: Ops::Request,
125 htype: HTypes::Ethernet,
126 hlen: 6,
127 hops: 0,
128 xid: self.get_xid(),
129 secs: 0,
130 flags: Flags { broadcast: false },
131 ciaddr: address, // MUST be client's IP address
132 yiaddr: Ipv4Addr::UNSPECIFIED,
133 siaddr: Ipv4Addr::UNSPECIFIED,
134 giaddr: Ipv4Addr::UNSPECIFIED,
135 chaddr: self.haddr,
136 sname: SName::EMPTY,
137 file: File::EMPTY,
138 magic: MAGIC,
139 options: vec![
140 MessageOptions::MessageType(MessageTypes::Request),
141 // MUST NOT fill in requested ip address
142 // MUST NOT fill in server identifier
143 ]
144 .into(),
145 }
146 }
147
148 /// Broadcast from client to server (during INIT-REBOOT) to verify a previously allocated, cached configuration.
149 // Server SHOULD send a DHCPNAK message to the client if the 'requested IP address'
150 // is incorrect, or is on the wrong network
151 pub fn verify(&mut self, address: Ipv4Addr) -> Message {
152 Message {
153 op: Ops::Request,
154 htype: HTypes::Ethernet,
155 hlen: 6,
156 hops: 0,
157 xid: self.get_xid(),
158 secs: 0,
159 flags: Flags { broadcast: false },
160 ciaddr: Ipv4Addr::UNSPECIFIED, // MUST be zero
161 yiaddr: Ipv4Addr::UNSPECIFIED,
162 siaddr: Ipv4Addr::UNSPECIFIED,
163 giaddr: Ipv4Addr::UNSPECIFIED,
164 chaddr: self.haddr,
165 sname: SName::EMPTY,
166 file: File::EMPTY,
167 magic: MAGIC,
168 options: vec![
169 MessageOptions::MessageType(MessageTypes::Request),
170 MessageOptions::RequestedIp(AddressOption::new(address)), // MUST be filled in with previously assigned address
171 // MUST NOT fill in server identifier
172 ]
173 .into(),
174 }
175 }
176
177 /// Broadcast from the client to server (during SELECTING) following an Offer message.
178 pub fn request(&self, message: Message) -> Message {
179 Message {
180 op: Ops::Request,
181 htype: HTypes::Ethernet,
182 hlen: 6,
183 hops: 0,
184 xid: message.xid,
185 secs: 0,
186 flags: Flags { broadcast: false }, // TODO or true if required
187 ciaddr: Ipv4Addr::UNSPECIFIED, // MUST be zero
188 yiaddr: Ipv4Addr::UNSPECIFIED,
189 siaddr: Ipv4Addr::UNSPECIFIED,
190 giaddr: Ipv4Addr::UNSPECIFIED,
191 chaddr: message.chaddr,
192 sname: SName::EMPTY,
193 file: File::EMPTY,
194 magic: MAGIC,
195 options: vec![
196 MessageOptions::MessageType(MessageTypes::Request),
197 MessageOptions::RequestedIp(AddressOption::new(message.yiaddr)), // MUST be filled in
198 MessageOptions::ServerIdentifier(AddressOption::new(message.siaddr)), // MUST be filled in
199 ]
200 .into(),
201 }
202 }
203
204 /// Sent from the client to server (following an Ack)
205 /// if the IP address is already in use.
206 pub fn decline(&self, message: Message) -> Message {
207 Message {
208 op: Ops::Request,
209 htype: HTypes::Ethernet,
210 hlen: 6,
211 hops: 0,
212 xid: message.xid,
213 secs: 0,
214 flags: Flags { broadcast: false },
215 ciaddr: Ipv4Addr::UNSPECIFIED,
216 yiaddr: Ipv4Addr::UNSPECIFIED,
217 siaddr: Ipv4Addr::UNSPECIFIED,
218 giaddr: Ipv4Addr::UNSPECIFIED,
219 chaddr: self.haddr,
220 sname: SName::EMPTY,
221 file: File::EMPTY,
222 magic: MAGIC,
223 options: vec![
224 MessageOptions::MessageType(MessageTypes::Decline),
225 MessageOptions::RequestedIp(AddressOption::new(message.yiaddr)), // MUST
226 MessageOptions::ServerIdentifier(AddressOption::new(message.siaddr)), // MUST
227 // Must not:
228 // - Requested IP address
229 // - IP address lease time
230 // - Vendor class identifier
231 // - Parameter request list
232 // - Maximum message size
233 // - Site-specific
234 // - All others (than: use file/sname, client identifier, and message)
235 ]
236 .into(),
237 }
238 }
239
240 /// Sent from the client to server.
241 pub fn release(&mut self) -> Message {
242 Message {
243 op: Ops::Request,
244 htype: HTypes::Ethernet,
245 hlen: 6,
246 hops: 0,
247 xid: self.get_xid(),
248 secs: 0,
249 flags: Flags { broadcast: false },
250 ciaddr: Ipv4Addr::UNSPECIFIED, // TODO client's network address
251 yiaddr: Ipv4Addr::UNSPECIFIED,
252 siaddr: Ipv4Addr::UNSPECIFIED,
253 giaddr: Ipv4Addr::UNSPECIFIED,
254 chaddr: self.haddr,
255 sname: SName::EMPTY,
256 file: File::EMPTY,
257 magic: MAGIC,
258 options: vec![
259 MessageOptions::MessageType(MessageTypes::Release),
260 MessageOptions::ServerIdentifier(AddressOption::new(Ipv4Addr::UNSPECIFIED)), // MUST, TODO server's address
261 // Must not:
262 // - Requested IP address
263 // - IP address lease time
264 // - Vendor class identifier
265 // - Parameter request list
266 // - Maximum message size
267 // - Site-specific
268 // - All others (than: use file/sname, client identifier, and message)
269 ]
270 .into(),
271 }
272 }
273
274 /// Sent from the client to server.
275 pub fn inform(&mut self, parameter_list: Vec<u8>) -> Message {
276 Message {
277 op: Ops::Request,
278 htype: HTypes::Ethernet,
279 hlen: 6,
280 hops: 0,
281 xid: self.get_xid(),
282 secs: 0,
283 flags: Flags { broadcast: false },
284 ciaddr: Ipv4Addr::UNSPECIFIED, // TODO client's network address
285 yiaddr: Ipv4Addr::UNSPECIFIED,
286 siaddr: Ipv4Addr::UNSPECIFIED,
287 giaddr: Ipv4Addr::UNSPECIFIED,
288 chaddr: self.haddr,
289 sname: SName::EMPTY,
290 file: File::EMPTY,
291 magic: MAGIC,
292 options: vec![
293 MessageOptions::MessageType(MessageTypes::Inform),
294 MessageOptions::RequestedOptions(parameter_list),
295 ]
296 .into(),
297 }
298 }
299}