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