Skip to main content

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}