VirtualBox

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

Last change on this file since 79818 was 79818, checked in by vboxsync, 5 years ago

Dhcpd: Fixed address assignments. bugref:9288

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