Skip to main content

toe_beans/v4/client/
mod.rs

1#![allow(missing_debug_implementations)]
2
3mod config;
4
5pub use config::*;
6
7use crate::v4::message::*;
8use mac_address::get_mac_address;
9use rand::RngCore;
10use rand::rngs::ThreadRng;
11use std::net::Ipv4Addr;
12
13/// The standard port for dhcpv4 clients defined in the RFC.
14pub const CLIENT_PORT: u16 = 68;
15/// This is an arbitrary port number above 1024 that can be used when testing the client.
16/// Port numbers below 1024 are privileged ports that require admin access to bind to.
17/// See: <https://www.w3.org/Daemon/User/Installation/PrivilegedPorts.html>
18pub const TEST_CLIENT_PORT: u16 = 8068;
19/// Resolves to TEST_PORT for debug builds and PORT for release builds
20pub const RESOLVED_CLIENT_PORT: u16 = if cfg!(debug_assertions) {
21    TEST_CLIENT_PORT
22} else {
23    CLIENT_PORT
24};
25
26fn get_chaddr() -> [u8; 16] {
27    let address = get_mac_address().unwrap().unwrap().bytes();
28    let mut chaddr: [u8; 16] = [0; 16];
29    chaddr[..6].copy_from_slice(&address[..6]);
30    chaddr
31}
32
33/// Logic for a DHCP client using this lib.
34///
35/// Used by the client binary and integration tests.
36///
37/// Requires the `v4_client` feature, which is enabled by default.
38pub struct Client {
39    /// A UdpSocket that understands how to use `Message` to communicate with the Server
40    pub socket: Socket,
41    /// The client's hardware address.
42    ///
43    /// This is cached for use in multiple messages to the server,
44    /// and the server will reuse it in its replies.
45    pub haddr: [u8; 16],
46    /// The random number generator used to generate the number for xid.
47    ///
48    /// Defaults to `ThreadRng`, but can be set to a custom value
49    /// with `Client.set_rng()`.
50    rng: Box<dyn RngCore>,
51    // TODO store current lease here
52}
53
54impl Client {
55    /// A wrapper around [UdpSocket::bind](https://doc.rust-lang.org/std/net/struct.UdpSocket.html#method.bind)
56    pub fn new(config: ClientConfig) -> Self {
57        Self {
58            socket: Socket::new(config.socket_config),
59            haddr: get_chaddr(),
60            rng: Box::new(ThreadRng::default()),
61        }
62    }
63
64    /// 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.
65    ///
66    /// For example, you could use [SmallRng](https://docs.rs/rand/latest/rand/rngs/struct.SmallRng.html)
67    /// which is sometimes faster, but has a higher collision rate of 1 in 65,000:
68    /// ```
69    /// use toe_beans::v4::{Client, ClientConfig};
70    /// use std::net::{Ipv4Addr, SocketAddrV4};
71    /// use rand::SeedableRng;
72    /// use rand::rngs::SmallRng;
73    ///
74    /// let client_config = ClientConfig::default();
75    /// let mut client = Client::new(client_config);
76    ///
77    /// // initialize the random number generator once.
78    /// let rng = SmallRng::from_os_rng();
79    ///
80    /// client.set_rng(Box::new(rng));
81    /// ```
82    pub fn set_rng(&mut self, rng: Box<dyn RngCore>) {
83        self.rng = rng;
84    }
85
86    /// Generates a number for xid by using the cached, initialized rng.
87    /// Note that generating a number mutates the internal state of the rng.
88    fn get_xid(&mut self) -> u32 {
89        self.rng.next_u32()
90    }
91
92    /// Sent from the client to server as the start of a DHCP conversation.
93    pub fn discover(&mut self) -> Message {
94        Message {
95            op: Ops::Request,
96            htype: HTypes::Ethernet,
97            hlen: 6,
98            hops: 0,
99            xid: self.get_xid(),
100            secs: 0,
101            flags: Flags { broadcast: false }, // TODO or true if required
102            ciaddr: Ipv4Addr::UNSPECIFIED,
103            yiaddr: Ipv4Addr::UNSPECIFIED,
104            siaddr: Ipv4Addr::UNSPECIFIED,
105            giaddr: Ipv4Addr::UNSPECIFIED,
106            chaddr: self.haddr,
107            sname: SName::EMPTY,
108            file: File::EMPTY,
109            magic: MAGIC,
110            options: vec![
111                MessageOptions::MessageType(MessageTypes::Discover),
112                // TODO may set option 50 to request an ip address
113            ]
114            .into(),
115        }
116    }
117
118    /// Sent from client to server (during RENEWING/REBINDING) when the client has a lease but wants to renew or rebind it.
119    ///
120    /// A renew will unicast, whereas a rebind will broadcast.
121    /// Rebinding is intended for multiple DHCP servers that can coordinate consistenency of their leases
122    pub fn extend(&mut self, address: Ipv4Addr) -> Message {
123        Message {
124            op: Ops::Request,
125            htype: HTypes::Ethernet,
126            hlen: 6,
127            hops: 0,
128            xid: self.get_xid(),
129            secs: 0,
130            flags: Flags { broadcast: false },
131            ciaddr: address, // MUST be client's IP address
132            yiaddr: Ipv4Addr::UNSPECIFIED,
133            siaddr: Ipv4Addr::UNSPECIFIED,
134            giaddr: Ipv4Addr::UNSPECIFIED,
135            chaddr: self.haddr,
136            sname: SName::EMPTY,
137            file: File::EMPTY,
138            magic: MAGIC,
139            options: vec![
140                MessageOptions::MessageType(MessageTypes::Request),
141                // MUST NOT fill in requested ip address
142                // MUST NOT fill in server identifier
143            ]
144            .into(),
145        }
146    }
147
148    /// Broadcast from client to server (during INIT-REBOOT) to verify a previously allocated, cached configuration.
149    // Server SHOULD send a DHCPNAK message to the client if the 'requested IP address'
150    // is incorrect, or is on the wrong network
151    pub fn verify(&mut self, address: Ipv4Addr) -> Message {
152        Message {
153            op: Ops::Request,
154            htype: HTypes::Ethernet,
155            hlen: 6,
156            hops: 0,
157            xid: self.get_xid(),
158            secs: 0,
159            flags: Flags { broadcast: false },
160            ciaddr: Ipv4Addr::UNSPECIFIED, // MUST be zero
161            yiaddr: Ipv4Addr::UNSPECIFIED,
162            siaddr: Ipv4Addr::UNSPECIFIED,
163            giaddr: Ipv4Addr::UNSPECIFIED,
164            chaddr: self.haddr,
165            sname: SName::EMPTY,
166            file: File::EMPTY,
167            magic: MAGIC,
168            options: vec![
169                MessageOptions::MessageType(MessageTypes::Request),
170                MessageOptions::RequestedIp(AddressOption::new(address)), // MUST be filled in with previously assigned address
171                                                                          // MUST NOT fill in server identifier
172            ]
173            .into(),
174        }
175    }
176
177    /// Broadcast from the client to server (during SELECTING) following an Offer message.
178    pub fn request(&self, message: Message) -> Message {
179        Message {
180            op: Ops::Request,
181            htype: HTypes::Ethernet,
182            hlen: 6,
183            hops: 0,
184            xid: message.xid,
185            secs: 0,
186            flags: Flags { broadcast: false }, // TODO or true if required
187            ciaddr: Ipv4Addr::UNSPECIFIED,     // MUST be zero
188            yiaddr: Ipv4Addr::UNSPECIFIED,
189            siaddr: Ipv4Addr::UNSPECIFIED,
190            giaddr: Ipv4Addr::UNSPECIFIED,
191            chaddr: message.chaddr,
192            sname: SName::EMPTY,
193            file: File::EMPTY,
194            magic: MAGIC,
195            options: vec![
196                MessageOptions::MessageType(MessageTypes::Request),
197                MessageOptions::RequestedIp(AddressOption::new(message.yiaddr)), // MUST be filled in
198                MessageOptions::ServerIdentifier(AddressOption::new(message.siaddr)), // MUST be filled in
199            ]
200            .into(),
201        }
202    }
203
204    /// Sent from the client to server (following an Ack)
205    /// if the IP address is already in use.
206    pub fn decline(&self, message: Message) -> Message {
207        Message {
208            op: Ops::Request,
209            htype: HTypes::Ethernet,
210            hlen: 6,
211            hops: 0,
212            xid: message.xid,
213            secs: 0,
214            flags: Flags { broadcast: false },
215            ciaddr: Ipv4Addr::UNSPECIFIED,
216            yiaddr: Ipv4Addr::UNSPECIFIED,
217            siaddr: Ipv4Addr::UNSPECIFIED,
218            giaddr: Ipv4Addr::UNSPECIFIED,
219            chaddr: self.haddr,
220            sname: SName::EMPTY,
221            file: File::EMPTY,
222            magic: MAGIC,
223            options: vec![
224                MessageOptions::MessageType(MessageTypes::Decline),
225                MessageOptions::RequestedIp(AddressOption::new(message.yiaddr)), // MUST
226                MessageOptions::ServerIdentifier(AddressOption::new(message.siaddr)), // MUST
227                                                                                 // Must not:
228                                                                                 // - Requested IP address
229                                                                                 // - IP address lease time
230                                                                                 // - Vendor class identifier
231                                                                                 // - Parameter request list
232                                                                                 // - Maximum message size
233                                                                                 // - Site-specific
234                                                                                 // - All others (than: use file/sname, client identifier, and message)
235            ]
236            .into(),
237        }
238    }
239
240    /// Sent from the client to server.
241    pub fn release(&mut self) -> Message {
242        Message {
243            op: Ops::Request,
244            htype: HTypes::Ethernet,
245            hlen: 6,
246            hops: 0,
247            xid: self.get_xid(),
248            secs: 0,
249            flags: Flags { broadcast: false },
250            ciaddr: Ipv4Addr::UNSPECIFIED, // TODO client's network address
251            yiaddr: Ipv4Addr::UNSPECIFIED,
252            siaddr: Ipv4Addr::UNSPECIFIED,
253            giaddr: Ipv4Addr::UNSPECIFIED,
254            chaddr: self.haddr,
255            sname: SName::EMPTY,
256            file: File::EMPTY,
257            magic: MAGIC,
258            options: vec![
259                MessageOptions::MessageType(MessageTypes::Release),
260                MessageOptions::ServerIdentifier(AddressOption::new(Ipv4Addr::UNSPECIFIED)), // MUST, TODO server's address
261                                                                                             // Must not:
262                                                                                             // - Requested IP address
263                                                                                             // - IP address lease time
264                                                                                             // - Vendor class identifier
265                                                                                             // - Parameter request list
266                                                                                             // - Maximum message size
267                                                                                             // - Site-specific
268                                                                                             // - All others (than: use file/sname, client identifier, and message)
269            ]
270            .into(),
271        }
272    }
273
274    /// Sent from the client to server.
275    pub fn inform(&mut self, parameter_list: Vec<u8>) -> Message {
276        Message {
277            op: Ops::Request,
278            htype: HTypes::Ethernet,
279            hlen: 6,
280            hops: 0,
281            xid: self.get_xid(),
282            secs: 0,
283            flags: Flags { broadcast: false },
284            ciaddr: Ipv4Addr::UNSPECIFIED, // TODO client's network address
285            yiaddr: Ipv4Addr::UNSPECIFIED,
286            siaddr: Ipv4Addr::UNSPECIFIED,
287            giaddr: Ipv4Addr::UNSPECIFIED,
288            chaddr: self.haddr,
289            sname: SName::EMPTY,
290            file: File::EMPTY,
291            magic: MAGIC,
292            options: vec![
293                MessageOptions::MessageType(MessageTypes::Inform),
294                MessageOptions::RequestedOptions(parameter_list),
295            ]
296            .into(),
297        }
298    }
299}