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 .into(),
99 }
100 }
101
102 /// Sent from client to server (during RENEWING/REBINDING) when the client has a lease but wants to renew or rebind it.
103 ///
104 /// A renew will unicast, whereas a rebind will broadcast.
105 /// Rebinding is intended for multiple DHCP servers that can coordinate consistenency of their leases
106 pub fn extend(&mut self, address: Ipv4Addr) -> Message {
107 Message {
108 op: Ops::Request,
109 htype: HTypes::Ethernet,
110 hlen: 6,
111 hops: 0,
112 xid: self.get_xid(),
113 secs: 0,
114 flags: Flags { broadcast: false },
115 ciaddr: address, // MUST be client's IP address
116 yiaddr: Ipv4Addr::UNSPECIFIED,
117 siaddr: Ipv4Addr::UNSPECIFIED,
118 giaddr: Ipv4Addr::UNSPECIFIED,
119 chaddr: self.haddr,
120 sname: SName::EMPTY,
121 file: File::EMPTY,
122 magic: MAGIC,
123 options: vec![
124 MessageOptions::MessageType(MessageTypes::Request),
125 // MUST NOT fill in requested ip address
126 // MUST NOT fill in server identifier
127 ]
128 .into(),
129 }
130 }
131
132 /// Broadcast from client to server (during INIT-REBOOT) to verify a previously allocated, cached configuration.
133 // Server SHOULD send a DHCPNAK message to the client if the 'requested IP address'
134 // is incorrect, or is on the wrong network
135 pub fn verify(&mut self, address: Ipv4Addr) -> Message {
136 Message {
137 op: Ops::Request,
138 htype: HTypes::Ethernet,
139 hlen: 6,
140 hops: 0,
141 xid: self.get_xid(),
142 secs: 0,
143 flags: Flags { broadcast: false },
144 ciaddr: Ipv4Addr::UNSPECIFIED, // MUST be zero
145 yiaddr: Ipv4Addr::UNSPECIFIED,
146 siaddr: Ipv4Addr::UNSPECIFIED,
147 giaddr: Ipv4Addr::UNSPECIFIED,
148 chaddr: self.haddr,
149 sname: SName::EMPTY,
150 file: File::EMPTY,
151 magic: MAGIC,
152 options: vec![
153 MessageOptions::MessageType(MessageTypes::Request),
154 MessageOptions::RequestedIp(AddressOption::new(address)), // MUST be filled in with previously assigned address
155 // MUST NOT fill in server identifier
156 ]
157 .into(),
158 }
159 }
160
161 /// Broadcast from the client to server (during SELECTING) following an Offer message.
162 pub fn request(&self, message: Message) -> Message {
163 Message {
164 op: Ops::Request,
165 htype: HTypes::Ethernet,
166 hlen: 6,
167 hops: 0,
168 xid: message.xid,
169 secs: 0,
170 flags: Flags { broadcast: false }, // TODO or true if required
171 ciaddr: Ipv4Addr::UNSPECIFIED, // MUST be zero
172 yiaddr: Ipv4Addr::UNSPECIFIED,
173 siaddr: Ipv4Addr::UNSPECIFIED,
174 giaddr: Ipv4Addr::UNSPECIFIED,
175 chaddr: message.chaddr,
176 sname: SName::EMPTY,
177 file: File::EMPTY,
178 magic: MAGIC,
179 options: vec![
180 MessageOptions::MessageType(MessageTypes::Request),
181 MessageOptions::RequestedIp(AddressOption::new(message.yiaddr)), // MUST be filled in
182 MessageOptions::ServerIdentifier(AddressOption::new(message.siaddr)), // MUST be filled in
183 ]
184 .into(),
185 }
186 }
187
188 /// Sent from the client to server (following an Ack)
189 /// if the IP address is already in use.
190 pub fn decline(&self, message: Message) -> Message {
191 Message {
192 op: Ops::Request,
193 htype: HTypes::Ethernet,
194 hlen: 6,
195 hops: 0,
196 xid: message.xid,
197 secs: 0,
198 flags: Flags { broadcast: false },
199 ciaddr: Ipv4Addr::UNSPECIFIED,
200 yiaddr: Ipv4Addr::UNSPECIFIED,
201 siaddr: Ipv4Addr::UNSPECIFIED,
202 giaddr: Ipv4Addr::UNSPECIFIED,
203 chaddr: self.haddr,
204 sname: SName::EMPTY,
205 file: File::EMPTY,
206 magic: MAGIC,
207 options: vec![
208 MessageOptions::MessageType(MessageTypes::Decline),
209 MessageOptions::RequestedIp(AddressOption::new(message.yiaddr)), // MUST
210 MessageOptions::ServerIdentifier(AddressOption::new(message.siaddr)), // MUST
211 // Must not:
212 // - Requested IP address
213 // - IP address lease time
214 // - Vendor class identifier
215 // - Parameter request list
216 // - Maximum message size
217 // - Site-specific
218 // - All others (than: use file/sname, client identifier, and message)
219 ]
220 .into(),
221 }
222 }
223
224 /// Sent from the client to server.
225 pub fn release(&mut self) -> Message {
226 Message {
227 op: Ops::Request,
228 htype: HTypes::Ethernet,
229 hlen: 6,
230 hops: 0,
231 xid: self.get_xid(),
232 secs: 0,
233 flags: Flags { broadcast: false },
234 ciaddr: Ipv4Addr::UNSPECIFIED, // TODO client's network address
235 yiaddr: Ipv4Addr::UNSPECIFIED,
236 siaddr: Ipv4Addr::UNSPECIFIED,
237 giaddr: Ipv4Addr::UNSPECIFIED,
238 chaddr: self.haddr,
239 sname: SName::EMPTY,
240 file: File::EMPTY,
241 magic: MAGIC,
242 options: vec![
243 MessageOptions::MessageType(MessageTypes::Release),
244 MessageOptions::ServerIdentifier(AddressOption::new(Ipv4Addr::UNSPECIFIED)), // MUST, TODO server's address
245 // Must not:
246 // - Requested IP address
247 // - IP address lease time
248 // - Vendor class identifier
249 // - Parameter request list
250 // - Maximum message size
251 // - Site-specific
252 // - All others (than: use file/sname, client identifier, and message)
253 ]
254 .into(),
255 }
256 }
257
258 /// Sent from the client to server.
259 pub fn inform(&mut self, parameter_list: Vec<u8>) -> Message {
260 Message {
261 op: Ops::Request,
262 htype: HTypes::Ethernet,
263 hlen: 6,
264 hops: 0,
265 xid: self.get_xid(),
266 secs: 0,
267 flags: Flags { broadcast: false },
268 ciaddr: Ipv4Addr::UNSPECIFIED, // TODO client's network address
269 yiaddr: Ipv4Addr::UNSPECIFIED,
270 siaddr: Ipv4Addr::UNSPECIFIED,
271 giaddr: Ipv4Addr::UNSPECIFIED,
272 chaddr: self.haddr,
273 sname: SName::EMPTY,
274 file: File::EMPTY,
275 magic: MAGIC,
276 options: vec![
277 MessageOptions::MessageType(MessageTypes::Inform),
278 MessageOptions::RequestedOptions(parameter_list),
279 ]
280 .into(),
281 }
282 }
283}