Kea 1.5.0
cfg_iface.cc
Go to the documentation of this file.
1// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8#include <dhcp/iface_mgr.h>
10#include <dhcpsrv/cfg_iface.h>
11#include <util/strutil.h>
12#include <boost/bind.hpp>
13#include <boost/foreach.hpp>
14#include <algorithm>
15
16using namespace isc::asiolink;
17using namespace isc::data;
18
19namespace isc {
20namespace dhcp {
21
22const char* CfgIface::ALL_IFACES_KEYWORD = "*";
23
25 : wildcard_used_(false), socket_type_(SOCKET_RAW), re_detect_(false),
26 outbound_iface_(SAME_AS_INBOUND) {
27}
28
29void
32}
33
34bool
35CfgIface::equals(const CfgIface& other) const {
36 return (iface_set_ == other.iface_set_ &&
37 address_map_ == other.address_map_ &&
38 wildcard_used_ == other.wildcard_used_ &&
39 socket_type_ == other.socket_type_);
40}
41
42bool
43CfgIface::multipleAddressesPerInterfaceActive() const {
45 BOOST_FOREACH(IfacePtr iface, ifaces) {
46 if (iface->countActive4() > 1) {
47 return (true);
48 }
49 }
50 return (false);
51}
52
53void
54CfgIface::openSockets(const uint16_t family, const uint16_t port,
55 const bool use_bcast) const {
56 // Close any open sockets because we're going to modify some properties
57 // of the IfaceMgr. Those modifications require that sockets are closed.
59 // The loopback interface can be used only when:
60 // - UDP socket will be used, i.e. not IPv4 and RAW socket
61 // - the loopback interface is in the interface set or the address map.
62 bool loopback_used_ = false;
63 if ((family == AF_INET6) || (socket_type_ == SOCKET_UDP)) {
64 // Check interface set
65 for (IfaceSet::const_iterator iface_name = iface_set_.begin();
66 iface_name != iface_set_.end(); ++iface_name) {
67 IfacePtr iface = IfaceMgr::instance().getIface(*iface_name);
68 if (iface && iface->flag_loopback_) {
69 loopback_used_ = true;
70 }
71 }
72 // Check address map
73 for (ExplicitAddressMap::const_iterator unicast = address_map_.begin();
74 unicast != address_map_.end(); ++unicast) {
75 IfacePtr iface = IfaceMgr::instance().getIface(unicast->first);
76 if (iface && iface->flag_loopback_) {
77 loopback_used_ = true;
78 }
79 }
80 }
81 // If wildcard interface '*' was not specified, set all interfaces to
82 // inactive state. We will later enable them selectively using the
83 // interface names specified by the user. If wildcard interface was
84 // specified, mark all interfaces active. Mark loopback inactive when
85 // not explicitely allowed.
86 setState(family, !wildcard_used_, !loopback_used_);
87 IfaceMgr& iface_mgr = IfaceMgr::instance();
88 // Remove selection of unicast addresses from all interfaces.
89 iface_mgr.clearUnicasts();
90 // Allow the loopback interface when required.
91 iface_mgr.setAllowLoopBack(loopback_used_);
92 // For the DHCPv4 server, if the user has selected that raw sockets
93 // should be used, we will try to configure the Interface Manager to
94 // support the direct responses to the clients that don't have the
95 // IP address. This should effectively turn on the use of raw
96 // sockets. However, this may be unsupported on some operating
97 // systems, so there is no guarantee.
98 if ((family == AF_INET) && (!IfaceMgr::instance().isTestMode())) {
99 iface_mgr.setMatchingPacketFilter(socket_type_ == SOCKET_RAW);
100 if ((socket_type_ == SOCKET_RAW) &&
101 !iface_mgr.isDirectResponseSupported()) {
102 LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_SOCKET_RAW_UNSUPPORTED);
103 }
104 }
105 // If there is no wildcard interface specified, we will have to iterate
106 // over the names specified by the caller and enable them.
107 if (!wildcard_used_) {
108 for (IfaceSet::const_iterator iface_name = iface_set_.begin();
109 iface_name != iface_set_.end(); ++iface_name) {
110 IfacePtr iface = IfaceMgr::instance().getIface(*iface_name);
111 // This shouldn't really happen because we are checking the
112 // names of interfaces when they are being added (use()
113 // function). But, if someone has triggered detection of
114 // interfaces since then, some interfaces may have disappeared.
115 if (iface == NULL) {
117 "fail to open socket on interface '"
118 << *iface_name << "' as this interface doesn't"
119 " exist");
120
121 } else if (family == AF_INET) {
122 iface->inactive4_ = false;
123 setIfaceAddrsState(family, true, *iface);
124
125 } else {
126 iface->inactive6_ = false;
127 }
128 }
129 }
130
131 // Select unicast sockets for DHCPv6 or activate specific IPv4 addresses
132 // for DHCPv4.
133 for (ExplicitAddressMap::const_iterator unicast = address_map_.begin();
134 unicast != address_map_.end(); ++unicast) {
135 IfacePtr iface = IfaceMgr::instance().getIface(unicast->first);
136 if (iface == NULL) {
138 "fail to open unicast socket on interface '"
139 << unicast->first << "' as this interface doesn't"
140 " exist");
141 }
142 if (family == AF_INET6) {
143 iface->addUnicast(unicast->second);
144 iface->inactive6_ = false;
145
146 } else {
147 iface->setActive(unicast->second, true);
148 iface->inactive4_ = false;
149 }
150 }
151
152 // Set the callback which is called when the socket fails to open
153 // for some specific interface. This callback will simply log a
154 // warning message.
155 IfaceMgrErrorMsgCallback error_callback =
156 boost::bind(&CfgIface::socketOpenErrorHandler, _1);
157 bool sopen;
158 if (family == AF_INET) {
159 // Use broadcast only if we're using raw sockets. For the UDP sockets,
160 // we only handle the relayed (unicast) traffic.
161 const bool can_use_bcast = use_bcast && (socket_type_ == SOCKET_RAW);
162 // Opening multiple raw sockets handling brodcast traffic on the single
163 // interface may lead to processing the same message multiple times.
164 // We don't prohibit such configuration because raw sockets can as well
165 // handle the relayed traffic. We have to issue a warning, however, to
166 // draw administrator's attention.
167 if (can_use_bcast && multipleAddressesPerInterfaceActive()) {
168 LOG_WARN(dhcpsrv_logger, DHCPSRV_MULTIPLE_RAW_SOCKETS_PER_IFACE);
169 }
170 sopen = IfaceMgr::instance().openSockets4(port, can_use_bcast, error_callback);
171 } else {
172 // use_bcast is ignored for V6.
173 sopen = IfaceMgr::instance().openSockets6(port, error_callback);
174 }
175
176 if (!sopen) {
177 // If no socket were opened, log a warning because the server will
178 // not respond to any queries.
179 LOG_WARN(dhcpsrv_logger, DHCPSRV_NO_SOCKETS_OPEN);
180 }
181}
182
183void
185 wildcard_used_ = false;
186 iface_set_.clear();
187 address_map_.clear();
188 useSocketType(AF_INET, SOCKET_RAW);
189}
190
191void
192CfgIface::setState(const uint16_t family, const bool inactive,
193 const bool loopback_inactive) const {
195 BOOST_FOREACH(IfacePtr iface, ifaces) {
196 bool iface_inactive = iface->flag_loopback_ ? loopback_inactive : inactive;
197 if (family == AF_INET) {
198 iface->inactive4_ = iface_inactive;
199 } else {
200 iface->inactive6_ = iface_inactive;
201 }
202
203 // Activate/deactivate all addresses.
204 setIfaceAddrsState(family, !inactive, *iface);
205 }
206}
207
208void
209CfgIface::setIfaceAddrsState(const uint16_t family, const bool active,
210 Iface& iface) const {
211 // Activate/deactivate all addresses.
212 BOOST_FOREACH(Iface::Address addr, iface.getAddresses()) {
213 if (addr.get().getFamily() == family) {
214 iface.setActive(addr.get(), active);
215 }
216 }
217}
218
219void
220CfgIface::socketOpenErrorHandler(const std::string& errmsg) {
221 LOG_WARN(dhcpsrv_logger, DHCPSRV_OPEN_SOCKET_FAIL).arg(errmsg);
222}
223
224std::string
226 switch (socket_type_) {
227 case SOCKET_RAW:
228 return ("raw");
229
230 case SOCKET_UDP:
231 return ("udp");
232
233 default:
234 ;
235 }
236
237 isc_throw(Unexpected, "unsupported socket type " << socket_type_);
238}
239
241CfgIface::textToSocketType(const std::string& socket_type_name) const {
242 if (socket_type_name == "udp") {
243 return (SOCKET_UDP);
244
245 } else if (socket_type_name == "raw") {
246 return (SOCKET_RAW);
247
248 } else {
249 isc_throw(InvalidSocketType, "unsupported socket type '"
250 << socket_type_name << "'");
251 }
252}
253
256 return (outbound_iface_);
257}
258
259std::string
261 switch (outbound_iface_) {
262 case SAME_AS_INBOUND:
263 return ("same-as-inbound");
264 case USE_ROUTING:
265 return ("use-routing");
266 default:
267 isc_throw(Unexpected, "unsupported outbound-type " << socket_type_);
268 }
269
270}
271
273CfgIface::textToOutboundIface(const std::string& txt) {
274 if (txt == "same-as-inbound") {
275 return (SAME_AS_INBOUND);
276
277 } else if (txt == "use-routing") {
278 return (USE_ROUTING);
279
280 } else {
281 isc_throw(BadValue, "unsupported outbound interface type '"
282 << txt << "'");
283 }
284}
285
286void
288 outbound_iface_ = outbound_iface;
289}
290
291void
292CfgIface::use(const uint16_t family, const std::string& iface_name) {
293 // The interface name specified may have two formats:
294 // - "interface-name", e.g. eth0
295 // - "interface-name/address", e.g. eth0/10.0.0.1 or eth/2001:db8:1::1
296 // The latter format is used to open unicast socket on the specified
297 // interface. Here we are detecting which format was used and we strip
298 // all extraneous spaces.
299 size_t pos = iface_name.find("/");
300 std::string name;
301 std::string addr_str;
302 // There is no unicast address so the whole string is an interface name.
303 if (pos == std::string::npos) {
304 name = util::str::trim(iface_name);
305 if (name.empty()) {
307 "empty interface name used in configuration");
308
309 } else if (name != ALL_IFACES_KEYWORD) {
310 if (IfaceMgr::instance().getIface(name) == NULL) {
311 isc_throw(NoSuchIface, "interface '" << name
312 << "' doesn't exist in the system");
313 }
314
315 } else if (wildcard_used_) {
316 isc_throw(DuplicateIfaceName, "the wildcard interface '"
317 << ALL_IFACES_KEYWORD << "' can only be specified once");
318
319 } else {
321 DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE);
322 wildcard_used_ = true;
323
324 }
325
326 } else {
327 // The interface name includes the address on which the socket should
328 // be opened, and we need to split interface name and the address to
329 // two variables.
330 name = util::str::trim(iface_name.substr(0, pos));
331 addr_str = util::str::trim(iface_name.substr(pos + 1));
332
333 // Interface name must not be empty.
334 if (name.empty()) {
336 "empty interface name specified in the"
337 " interface configuration");
338
339 }
340 // An address following the interface name must not be empty.
341 if (addr_str.empty()) {
343 "empty address specified in the interface"
344 << " configuration");
345
346 }
347
348 // Interface name must not be the wildcard name.
349 if (name == ALL_IFACES_KEYWORD) {
351 "wildcard interface name '" << ALL_IFACES_KEYWORD
352 << "' must not be used in conjunction with an"
353 " address");
354
355 }
356
357 // Interface must exist.
358 IfacePtr iface = IfaceMgr::instance().getIface(name);
359 if (!iface) {
360 isc_throw(NoSuchIface, "interface '" << name
361 << "' doesn't exist in the system");
362
363 }
364
365 // Convert address string. This may throw an exception if the address
366 // is invalid.
367 IOAddress addr(addr_str);
368
369 // Validate V6 address.
370 if (family == AF_INET6) {
371 // Check that the address is a valid unicast address.
372 if (!addr.isV6() || addr.isV6Multicast()) {
373 isc_throw(InvalidIfaceName, "address '" << addr << "' is not"
374 " a valid IPv6 unicast address");
375 }
376
377 // There are valid cases where link local address can be specified to
378 // receive unicast traffic, e.g. sent by relay agent.
379 if (addr.isV6LinkLocal()) {
380 LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL)
381 .arg(addr.toText()).arg(name);
382 }
383
384 } else {
385 if (!addr.isV4()) {
386 isc_throw(InvalidIfaceName, "address '" << addr << "' is not"
387 " a valid IPv4 address");
388 }
389 }
390
391 // Interface must have this address assigned.
392 if (!iface->hasAddress(addr)) {
394 "interface '" << name << "' doesn't have address '"
395 << addr << "' assigned");
396 }
397
398 // For the IPv4, if the interface name was specified (instead of the interface-
399 // address tuple) all addresses are already activated. Adding an explicit address
400 // for the interface should result in error.
401 if ((family == AF_INET) && (iface_set_.find(iface->getName()) != iface_set_.end())) {
402 isc_throw(DuplicateIfaceName, "interface '" << iface->getName()
403 << "' has already been selected");
404 }
405
406 // Check if the address hasn't been selected already.
407 std::pair<const std::string, IOAddress> iface_address_tuple(name, addr);
408 if (std::find(address_map_.begin(), address_map_.end(),
409 iface_address_tuple) != address_map_.end()) {
410 isc_throw(DuplicateAddress, "must not select address '"
411 << addr << "' for interface '" << name << "' "
412 "because this address is already selected");
413 }
414
415 if (family == AF_INET6) {
416 LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_USE_UNICAST)
417 .arg(addr.toText()).arg(name);
418
419 } else {
420 LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_USE_ADDRESS)
421 .arg(addr.toText()).arg(name);
422 }
423 address_map_.insert(std::pair<std::string, IOAddress>(name, addr));
424 }
425
426 // If interface name was explicitly specified without an address, we will
427 // insert the interface name to the set of enabled interfaces.
428 if ((name != ALL_IFACES_KEYWORD) && addr_str.empty()) {
429 // An interface has been selected or an IPv4 address on this interface
430 // has been selected it is not allowed to select the whole interface.
431 if ((iface_set_.find(name) != iface_set_.end()) ||
432 ((family == AF_INET) && address_map_.count(name) > 0)) {
433 isc_throw(DuplicateIfaceName, "interface '" << name
434 << "' has already been specified");
435 }
436
437 // Log that we're listening on the specific interface and that the
438 // address is not explicitly specified.
439 if (addr_str.empty()) {
440 LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_ADD_IFACE).arg(name);
441 }
442 iface_set_.insert(name);
443 }
444}
445
446void
447CfgIface::useSocketType(const uint16_t family,
448 const SocketType& socket_type) {
449 if (family != AF_INET) {
450 isc_throw(InvalidSocketType, "socket type must not be specified for"
451 " the DHCPv6 server");
452 }
453 socket_type_ = socket_type;
454 LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_SOCKET_TYPE_SELECT)
455 .arg(socketTypeToText());
456}
457
458void
459CfgIface::useSocketType(const uint16_t family,
460 const std::string& socket_type_name) {
461 useSocketType(family, textToSocketType(socket_type_name));
462}
463
467
468 // Set user context
469 contextToElement(result);
470
471 // Set interfaces
473 if (wildcard_used_) {
474 ifaces->add(Element::create(std::string(ALL_IFACES_KEYWORD)));
475 }
476 for (IfaceSet::const_iterator iface = iface_set_.cbegin();
477 iface != iface_set_.cend(); ++iface) {
478 ifaces->add(Element::create(*iface));
479 }
480 for (ExplicitAddressMap::const_iterator address = address_map_.cbegin();
481 address != address_map_.cend(); ++address) {
482 std::string spec = address->first + "/" + address->second.toText();
483 ifaces->add(Element::create(spec));
484 }
485 result->set("interfaces", ifaces);
486
487 // Set dhcp-socket-type (no default because it is DHCPv4 specific)
488 // @todo emit raw if and only if DHCPv4
489 if (socket_type_ != SOCKET_RAW) {
490 result->set("dhcp-socket-type", Element::create(std::string("udp")));
491 }
492
493 if (outbound_iface_ != SAME_AS_INBOUND) {
494 result->set("outbound-interface", Element::create(outboundTypeToText()));
495 }
496
497 // Set re-detect
498 result->set("re-detect", Element::create(re_detect_));
499
500 return (result);
501}
502
503} // end of isc::dhcp namespace
504} // end of isc namespace
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
A generic exception that is thrown when an unexpected error condition occurs.
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition: data.cc:223
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition: data.cc:268
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition: data.cc:263
Represents selection of interfaces for DHCP server.
Definition: cfg_iface.h:130
static const char * ALL_IFACES_KEYWORD
Keyword used to enable all interfaces.
Definition: cfg_iface.h:154
void closeSockets() const
Convenience function which closes all open sockets.
Definition: cfg_iface.cc:30
std::string socketTypeToText() const
Returns the socket type in the textual format.
Definition: cfg_iface.cc:225
void reset()
Puts the interface configuration into default state.
Definition: cfg_iface.cc:184
OutboundIface
Indicates how outbound interface is selected for relayed traffic.
Definition: cfg_iface.h:142
@ USE_ROUTING
Server uses routing to determine the right interface to send response.
Definition: cfg_iface.h:147
@ SAME_AS_INBOUND
Server sends responses over the same interface on which queries are received.
Definition: cfg_iface.h:145
OutboundIface getOutboundIface() const
Returns outbound interface selection mode.
Definition: cfg_iface.cc:255
CfgIface()
Constructor.
Definition: cfg_iface.cc:24
static OutboundIface textToOutboundIface(const std::string &txt)
Converts text to outbound interface selection mode.
Definition: cfg_iface.cc:273
void use(const uint16_t family, const std::string &iface_name)
Select interface to be used to receive DHCP traffic.
Definition: cfg_iface.cc:292
bool equals(const CfgIface &other) const
Compares two CfgIface objects for equality.
Definition: cfg_iface.cc:35
SocketType
Socket type used by the DHCPv4 server.
Definition: cfg_iface.h:134
@ SOCKET_UDP
Datagram socket, i.e. IP/UDP socket.
Definition: cfg_iface.h:138
@ SOCKET_RAW
Raw socket, used for direct DHCPv4 traffic.
Definition: cfg_iface.h:136
virtual isc::data::ElementPtr toElement() const
Unparse a configuration object.
Definition: cfg_iface.cc:465
std::string outboundTypeToText() const
Returns outbound interface selection mode as string.
Definition: cfg_iface.cc:260
void setOutboundIface(const OutboundIface &outbound_iface)
Sets outbound interface selection mode.
Definition: cfg_iface.cc:287
SocketType textToSocketType(const std::string &socket_type_name) const
Converts the socket type in the textual format to the type represented by the SocketType.
Definition: cfg_iface.cc:241
void useSocketType(const uint16_t family, const SocketType &socket_type)
Sets the specified socket type to be used by the server.
Definition: cfg_iface.cc:447
void openSockets(const uint16_t family, const uint16_t port, const bool use_bcast=true) const
Tries to open sockets on selected interfaces.
Definition: cfg_iface.cc:54
Exception thrown when duplicated address specified.
Definition: cfg_iface.h:44
Exception thrown when duplicated interface names specified.
Definition: cfg_iface.h:23
Handles network interfaces, transmission and reception.
Definition: iface_mgr.h:478
const IfaceCollection & getIfaces()
Returns container with all interfaces.
Definition: iface_mgr.h:597
bool openSockets6(const uint16_t port=DHCP6_SERVER_PORT, IfaceMgrErrorMsgCallback error_handler=0)
Opens IPv6 sockets on detected interfaces.
Definition: iface_mgr.cc:606
void clearUnicasts()
Clears unicast addresses on all interfaces.
Definition: iface_mgr.cc:777
std::list< IfacePtr > IfaceCollection
Type that holds a list of pointers to interfaces.
Definition: iface_mgr.h:509
bool openSockets4(const uint16_t port=DHCP4_SERVER_PORT, const bool use_bcast=true, IfaceMgrErrorMsgCallback error_handler=0)
Opens IPv4 sockets on detected interfaces.
Definition: iface_mgr.cc:477
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition: iface_mgr.cc:53
bool isDirectResponseSupported() const
Check if packet be sent directly to the client having no address.
Definition: iface_mgr.cc:312
IfacePtr getIface(int ifindex)
Returns interface specified interface index.
Definition: iface_mgr.cc:752
void closeSockets()
Closes all open sockets.
Definition: iface_mgr.cc:282
void setMatchingPacketFilter(const bool direct_response_desired=false)
Set Packet Filter object to handle send/receive packets.
void setAllowLoopBack(const bool allow_loopback)
Allows or disallows the loopback interface.
Definition: iface_mgr.h:559
util::OptionalValue< asiolink::IOAddress > Address
Address type.
Definition: iface_mgr.h:121
Exception thrown when specified interface name is invalid.
Definition: cfg_iface.h:30
Exception thrown when invalid socket type has been specified for the given family.
Definition: cfg_iface.h:60
Exception thrown when specified unicast address is not assigned to the interface specified.
Definition: cfg_iface.h:52
Exception thrown when specified interface doesn't exist in a system.
Definition: cfg_iface.h:37
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition: macros.h:26
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
boost::shared_ptr< Element > ElementPtr
Definition: data.h:22
isc::log::Logger dhcpsrv_logger("dhcpsrv")
DHCP server library Logger.
Definition: dhcpsrv_log.h:56
boost::shared_ptr< Iface > IfacePtr
Definition: iface_mgr.h:457
boost::function< void(const std::string &errmsg)> IfaceMgrErrorMsgCallback
This type describes the callback function invoked when error occurs in the IfaceMgr.
Definition: iface_mgr.h:470
const int DHCPSRV_DBG_TRACE
DHCP server library logging levels.
Definition: dhcpsrv_log.h:26
string trim(const string &instring)
Trim Leading and Trailing Spaces.
Definition: strutil.cc:53
Defines the logger used by the top-level component of kea-dhcp-ddns.
void contextToElement(data::ElementPtr map) const
Merge unparse a user_context object.
Definition: user_context.cc:15