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