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