VirtualBox

source: vbox/trunk/src/VBox/NetworkServices/Dhcpd/DHCPD.cpp

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 12.5 KB
Line 
1/* $Id: DHCPD.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * DHCP server - protocol logic
4 */
5
6/*
7 * Copyright (C) 2017-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#include "DhcpdInternal.h"
33#include "DHCPD.h"
34#include "DhcpOptions.h"
35
36#include <iprt/message.h>
37
38
39DHCPD::DHCPD()
40 : m_pConfig(NULL), m_db()
41{
42}
43
44
45/**
46 * Initializes the DHCPD with the given config.
47 *
48 * @returns VBox status code.
49 * @param pConfig The configuration to use.
50 */
51int DHCPD::init(const Config *pConfig) RT_NOEXCEPT
52{
53 Assert(pConfig);
54 AssertReturn(!m_pConfig, VERR_INVALID_STATE);
55 m_pConfig = pConfig;
56
57 /* Load the lease database, ignoring most issues except being out of memory: */
58 int rc = m_db.init(pConfig);
59 if (RT_SUCCESS(rc))
60 {
61 rc = i_loadLeases();
62 if (rc != VERR_NO_MEMORY)
63 return VINF_SUCCESS;
64
65 DHCP_LOG_MSG_ERROR(("Ran out of memory loading leases from '%s'. Try rename or delete the file.\n",
66 pConfig->getLeasesFilename().c_str()));
67 }
68 return rc;
69}
70
71
72/**
73 * Load leases from pConfig->getLeasesFilename().
74 */
75int DHCPD::i_loadLeases() RT_NOEXCEPT
76{
77 return m_db.loadLeases(m_pConfig->getLeasesFilename());
78}
79
80
81/**
82 * Save the current leases to pConfig->getLeasesFilename(), doing expiry first.
83 *
84 * This is called after m_db is updated during a client request, so the on disk
85 * database is always up-to-date. This means it doesn't matter if we're
86 * terminated with extreme prejudice, and it allows Main to look up IP addresses
87 * for VMs.
88 *
89 * @throws nothing
90 */
91void DHCPD::i_saveLeases() RT_NOEXCEPT
92{
93 m_db.expire();
94 m_db.writeLeases(m_pConfig->getLeasesFilename());
95}
96
97
98/**
99 * Process a DHCP client message.
100 *
101 * Called by VBoxNetDhcpd::dhcp4Recv().
102 *
103 * @returns Pointer to DHCP reply (caller deletes this). NULL if no reply
104 * warranted or we're out of memory.
105 * @param req The client message.
106 * @throws nothing
107 */
108DhcpServerMessage *DHCPD::process(DhcpClientMessage &req) RT_NOEXCEPT
109{
110 /*
111 * Dump the package if release log level 3+1 are enable or if debug logging is
112 * enabled. We don't normally want to do this at the default log level, of course.
113 */
114 if ((LogRelIs3Enabled() && LogRelIsEnabled()) || LogIsEnabled())
115 req.dump();
116
117 /*
118 * Fend off requests that are not for us.
119 */
120 OptServerId sid(req);
121 if (sid.present() && sid.value().u != m_pConfig->getIPv4Address().u)
122 {
123 if (req.broadcasted() && req.messageType() == RTNET_DHCP_MT_REQUEST)
124 {
125 LogRel2(("Message is not for us, canceling any pending offer.\n"));
126 m_db.cancelOffer(req);
127 }
128 else
129 LogRel2(("Message is not for us.\n"));
130 return NULL;
131 }
132
133 /*
134 * Process it.
135 */
136 DhcpServerMessage *reply = NULL;
137
138 switch (req.messageType())
139 {
140 /*
141 * Requests that require server's reply.
142 */
143 case RTNET_DHCP_MT_DISCOVER:
144 try
145 {
146 reply = i_doDiscover(req);
147 }
148 catch (std::bad_alloc &)
149 {
150 LogRelFunc(("i_doDiscover threw bad_alloc\n"));
151 }
152 break;
153
154 case RTNET_DHCP_MT_REQUEST:
155 try
156 {
157 reply = i_doRequest(req);
158 }
159 catch (std::bad_alloc &)
160 {
161 LogRelFunc(("i_doRequest threw bad_alloc\n"));
162 }
163 break;
164
165 case RTNET_DHCP_MT_INFORM:
166 try
167 {
168 reply = i_doInform(req);
169 }
170 catch (std::bad_alloc &)
171 {
172 LogRelFunc(("i_doInform threw bad_alloc\n"));
173 }
174 break;
175
176 /*
177 * Requests that don't have a reply.
178 */
179 case RTNET_DHCP_MT_DECLINE:
180 i_doDecline(req);
181 break;
182
183 case RTNET_DHCP_MT_RELEASE:
184 i_doRelease(req);
185 break;
186
187 /*
188 * Unexpected or unknown message types.
189 */
190 case RTNET_DHCP_MT_OFFER:
191 LogRel2(("Ignoring unexpected message of type RTNET_DHCP_MT_OFFER!\n"));
192 break;
193 case RTNET_DHCP_MT_ACK:
194 LogRel2(("Ignoring unexpected message of type RTNET_DHCP_MT_ACK!\n"));
195 break;
196 case RTNET_DHCP_MT_NAC:
197 LogRel2(("Ignoring unexpected message of type RTNET_DHCP_MT_NAC!\n"));
198 break;
199 default:
200 LogRel2(("Ignoring unexpected message of unknown type: %d (%#x)!\n", req.messageType(), req.messageType()));
201 break;
202 }
203
204 return reply;
205}
206
207
208/**
209 * Internal helper.
210 *
211 * @throws std::bad_alloc
212 */
213DhcpServerMessage *DHCPD::i_createMessage(int type, const DhcpClientMessage &req)
214{
215 return new DhcpServerMessage(req, type, m_pConfig->getIPv4Address());
216}
217
218
219/**
220 * 4.3.1 DHCPDISCOVER message
221 *
222 * When a server receives a DHCPDISCOVER message from a client, the server
223 * chooses a network address for the requesting client. If no address is
224 * available, the server may choose to report the problem to the system
225 * administrator. If an address is available, the new address SHOULD be chosen
226 * as follows:
227 * - The client's current address as recorded in the client's current binding,
228 * ELSE
229 * - The client's previous address as recorded in the client's (now expired or
230 * released) binding, if that address is in the server's pool of available
231 * addresses and not already allocated, ELSE
232 * - The address requested in the 'Requested IP Address' option, if that
233 * address is valid and not already allocated, ELSE
234 * - A new address allocated from the server's pool of available addresses;
235 * the address is selected based on the subnet from which the message was
236 * received (if 'giaddr' is 0) or on the address of the relay agent that
237 * forwarded the message ('giaddr' when not 0).
238 *
239 * ...
240 *
241 * @throws std::bad_alloc
242 */
243DhcpServerMessage *DHCPD::i_doDiscover(const DhcpClientMessage &req)
244{
245 /** @todo
246 * XXX: TODO: Windows iSCSI initiator sends DHCPDISCOVER first and
247 * it has ciaddr filled. Shouldn't let it screw up the normal
248 * lease we already have for that client, but we should probably
249 * reply with a pro-forma offer.
250 */
251 if (req.ciaddr().u != 0)
252 return NULL;
253
254 Config::ConfigVec vecConfigs;
255 m_pConfig->getConfigsForClient(vecConfigs, req.clientId(), OptVendorClassId(req), OptUserClassId(req));
256
257 Binding *b = m_db.allocateBinding(req, vecConfigs);
258 if (b == NULL)
259 return NULL;
260
261 std::unique_ptr<DhcpServerMessage> reply;
262
263 bool fRapidCommit = OptRapidCommit(req).present();
264 if (!fRapidCommit)
265 {
266 reply.reset(i_createMessage(RTNET_DHCP_MT_OFFER, req));
267
268 if (b->state() < Binding::OFFERED)
269 b->setState(Binding::OFFERED);
270
271 /** @todo use small lease time internally to quickly free unclaimed offers? */
272 }
273 else
274 {
275 reply.reset(i_createMessage(RTNET_DHCP_MT_ACK, req));
276 reply->addOption(OptRapidCommit(true));
277
278 b->setState(Binding::ACKED);
279 if (!b->isFixed())
280 i_saveLeases();
281 }
282
283 reply->setYiaddr(b->addr());
284 reply->addOption(OptLeaseTime(b->leaseTime()));
285
286 OptParameterRequest optlist(req);
287 optmap_t replyOptions;
288 reply->addOptions(m_pConfig->getOptionsForClient(replyOptions, optlist, vecConfigs));
289
290 // reply->maybeUnicast(req); /** @todo XXX: we reject ciaddr != 0 above */
291 return reply.release();
292}
293
294
295/**
296 * 4.3.2 DHCPREQUEST message
297 *
298 * A DHCPREQUEST message may come from a client responding to a DHCPOFFER
299 * message from a server, from a client verifying a previously allocated IP
300 * address or from a client extending the lease on a network address. If the
301 * DHCPREQUEST message contains a 'server identifier' option, the message is in
302 * response to a DHCPOFFER message. Otherwise, the message is a request to
303 * verify or extend an existing lease. If the client uses a 'client identifier'
304 * in a DHCPREQUEST message, it MUST use that same 'client identifier' in all
305 * subsequent messages. If the client included a list of requested parameters in
306 * a DHCPDISCOVER message, it MUST include that list in all subsequent messages.
307 *
308 * ...
309 *
310 * @throws std::bad_alloc
311 */
312DhcpServerMessage *DHCPD::i_doRequest(const DhcpClientMessage &req)
313{
314 OptRequestedAddress reqAddr(req);
315 if (req.ciaddr().u != 0 && reqAddr.present() && reqAddr.value().u != req.ciaddr().u)
316 {
317 std::unique_ptr<DhcpServerMessage> nak(i_createMessage(RTNET_DHCP_MT_NAC, req));
318 nak->addOption(OptMessage("Requested address does not match ciaddr"));
319 return nak.release();
320 }
321
322 Config::ConfigVec vecConfigs;
323 m_pConfig->getConfigsForClient(vecConfigs, req.clientId(), OptVendorClassId(req), OptUserClassId(req));
324
325 Binding *b = m_db.allocateBinding(req, vecConfigs);
326 if (b == NULL)
327 {
328 return i_createMessage(RTNET_DHCP_MT_NAC, req);
329 }
330
331 std::unique_ptr<DhcpServerMessage> ack(i_createMessage(RTNET_DHCP_MT_ACK, req));
332
333 b->setState(Binding::ACKED);
334 if (!b->isFixed())
335 i_saveLeases();
336
337 ack->setYiaddr(b->addr());
338 ack->addOption(OptLeaseTime(b->leaseTime()));
339
340 OptParameterRequest optlist(req);
341 optmap_t replyOptions;
342 ack->addOptions(m_pConfig->getOptionsForClient(replyOptions, optlist, vecConfigs));
343
344 ack->maybeUnicast(req);
345 return ack.release();
346}
347
348
349/**
350 * 4.3.5 DHCPINFORM message
351 *
352 * The server responds to a DHCPINFORM message by sending a DHCPACK message
353 * directly to the address given in the 'ciaddr' field of the DHCPINFORM
354 * message. The server MUST NOT send a lease expiration time to the client and
355 * SHOULD NOT fill in 'yiaddr'. The server includes other parameters in the
356 * DHCPACK message as defined in section 4.3.1.
357 *
358 * @throws std::bad_alloc
359 */
360DhcpServerMessage *DHCPD::i_doInform(const DhcpClientMessage &req)
361{
362 if (req.ciaddr().u == 0)
363 return NULL;
364
365 OptParameterRequest optlist(req);
366 if (!optlist.present())
367 return NULL;
368
369 Config::ConfigVec vecConfigs;
370 optmap_t info;
371 m_pConfig->getOptionsForClient(info, optlist, m_pConfig->getConfigsForClient(vecConfigs, req.clientId(),
372 OptVendorClassId(req), OptUserClassId(req)));
373 if (info.empty())
374 return NULL;
375
376 std::unique_ptr<DhcpServerMessage> ack(i_createMessage(RTNET_DHCP_MT_ACK, req));
377 ack->addOptions(info);
378 ack->maybeUnicast(req);
379 return ack.release();
380}
381
382
383/**
384 * 4.3.3 DHCPDECLINE message
385 *
386 * If the server receives a DHCPDECLINE message, the client has discovered
387 * through some other means that the suggested network address is already in
388 * use. The server MUST mark the network address as not available and SHOULD
389 * notify the local system administrator of a possible configuration problem.
390 *
391 * @throws nothing
392 */
393DhcpServerMessage *DHCPD::i_doDecline(const DhcpClientMessage &req) RT_NOEXCEPT
394{
395 RT_NOREF(req);
396 return NULL;
397}
398
399
400/**
401 * 4.3.4 DHCPRELEASE message
402 *
403 * Upon receipt of a DHCPRELEASE message, the server marks the network address
404 * as not allocated. The server SHOULD retain a record of the client's
405 * initialization parameters for possible reuse in response to subsequent
406 * requests from the client.
407 *
408 * @throws nothing
409 */
410DhcpServerMessage *DHCPD::i_doRelease(const DhcpClientMessage &req) RT_NOEXCEPT
411{
412 if (req.ciaddr().u != 0)
413 {
414 bool fReleased = m_db.releaseBinding(req);
415 if (fReleased)
416 i_saveLeases();
417 }
418
419 return NULL;
420}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette