toe_beans/v4/client/
client.rs

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