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            .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}