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}