1 // Copyright (C) 2014-2021 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>
9 #include <dhcpsrv/dhcpsrv_log.h>
10 #include <dhcpsrv/cfg_iface.h>
11 #include <util/strutil.h>
12 #include <algorithm>
13 #include <functional>
14
15 using namespace isc::asiolink;
16 using namespace isc::data;
17 namespace ph = std::placeholders;
18
19 namespace isc {
20 namespace dhcp {
21
22 const char* CfgIface::ALL_IFACES_KEYWORD = "*";
23
CfgIface()24 CfgIface::CfgIface()
25 : wildcard_used_(false), socket_type_(SOCKET_RAW), re_detect_(false),
26 outbound_iface_(SAME_AS_INBOUND) {
27 }
28
29 void
closeSockets() const30 CfgIface::closeSockets() const {
31 IfaceMgr::instance().closeSockets();
32 }
33
34 bool
equals(const CfgIface & other) const35 CfgIface::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
42 bool
multipleAddressesPerInterfaceActive() const43 CfgIface::multipleAddressesPerInterfaceActive() const {
44 for (IfacePtr iface : IfaceMgr::instance().getIfaces()) {
45 if (iface->countActive4() > 1) {
46 return (true);
47 }
48 }
49 return (false);
50 }
51
52 void
openSockets(const uint16_t family,const uint16_t port,const bool use_bcast) const53 CfgIface::openSockets(const uint16_t family, const uint16_t port,
54 const bool use_bcast) const {
55 // Close any open sockets because we're going to modify some properties
56 // of the IfaceMgr. Those modifications require that sockets are closed.
57 closeSockets();
58 // The loopback interface can be used only when:
59 // - UDP socket will be used, i.e. not IPv4 and RAW socket
60 // - the loopback interface is in the interface set or the address map.
61 bool loopback_used_ = false;
62 if ((family == AF_INET6) || (socket_type_ == SOCKET_UDP)) {
63 // Check interface set
64 for (IfaceSet::const_iterator iface_name = iface_set_.begin();
65 iface_name != iface_set_.end(); ++iface_name) {
66 IfacePtr iface = IfaceMgr::instance().getIface(*iface_name);
67 if (iface && iface->flag_loopback_) {
68 loopback_used_ = true;
69 }
70 }
71 // Check address map
72 for (ExplicitAddressMap::const_iterator unicast = address_map_.begin();
73 unicast != address_map_.end(); ++unicast) {
74 IfacePtr iface = IfaceMgr::instance().getIface(unicast->first);
75 if (iface && iface->flag_loopback_) {
76 loopback_used_ = true;
77 }
78 }
79 }
80 // If wildcard interface '*' was not specified, set all interfaces to
81 // inactive state. We will later enable them selectively using the
82 // interface names specified by the user. If wildcard interface was
83 // specified, mark all interfaces active. Mark loopback inactive when
84 // not explicitly allowed.
85 setState(family, !wildcard_used_, !loopback_used_);
86 IfaceMgr& iface_mgr = IfaceMgr::instance();
87 // Remove selection of unicast addresses from all interfaces.
88 iface_mgr.clearUnicasts();
89 // Allow the loopback interface when required.
90 iface_mgr.setAllowLoopBack(loopback_used_);
91 // For the DHCPv4 server, if the user has selected that raw sockets
92 // should be used, we will try to configure the Interface Manager to
93 // support the direct responses to the clients that don't have the
94 // IP address. This should effectively turn on the use of raw
95 // sockets. However, this may be unsupported on some operating
96 // systems, so there is no guarantee.
97 if ((family == AF_INET) && (!IfaceMgr::instance().isTestMode())) {
98 iface_mgr.setMatchingPacketFilter(socket_type_ == SOCKET_RAW);
99 if ((socket_type_ == SOCKET_RAW) &&
100 !iface_mgr.isDirectResponseSupported()) {
101 LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_SOCKET_RAW_UNSUPPORTED);
102 }
103 }
104 // If there is no wildcard interface specified, we will have to iterate
105 // over the names specified by the caller and enable them.
106 if (!wildcard_used_) {
107 for (IfaceSet::const_iterator iface_name = iface_set_.begin();
108 iface_name != iface_set_.end(); ++iface_name) {
109 IfacePtr iface = IfaceMgr::instance().getIface(*iface_name);
110 // This shouldn't really happen because we are checking the
111 // names of interfaces when they are being added (use()
112 // function). But, if someone has triggered detection of
113 // interfaces since then, some interfaces may have disappeared.
114 if (iface == NULL) {
115 isc_throw(Unexpected,
116 "fail to open socket on interface '"
117 << *iface_name << "' as this interface doesn't"
118 " exist");
119
120 } else if (family == AF_INET) {
121 iface->inactive4_ = false;
122 setIfaceAddrsState(family, true, *iface);
123
124 } else {
125 iface->inactive6_ = false;
126 }
127 }
128 }
129
130 // Select unicast sockets for DHCPv6 or activate specific IPv4 addresses
131 // for DHCPv4.
132 for (ExplicitAddressMap::const_iterator unicast = address_map_.begin();
133 unicast != address_map_.end(); ++unicast) {
134 IfacePtr iface = IfaceMgr::instance().getIface(unicast->first);
135 if (iface == NULL) {
136 isc_throw(Unexpected,
137 "fail to open unicast socket on interface '"
138 << unicast->first << "' as this interface doesn't"
139 " exist");
140 }
141 if (family == AF_INET6) {
142 iface->addUnicast(unicast->second);
143 iface->inactive6_ = false;
144
145 } else {
146 iface->setActive(unicast->second, true);
147 iface->inactive4_ = false;
148 }
149 }
150
151 // Set the callback which is called when the socket fails to open
152 // for some specific interface. This callback will simply log a
153 // warning message.
154 IfaceMgrErrorMsgCallback error_callback =
155 std::bind(&CfgIface::socketOpenErrorHandler, ph::_1);
156 bool sopen;
157 if (family == AF_INET) {
158 // Use broadcast only if we're using raw sockets. For the UDP sockets,
159 // we only handle the relayed (unicast) traffic.
160 const bool can_use_bcast = use_bcast && (socket_type_ == SOCKET_RAW);
161 // Opening multiple raw sockets handling brodcast traffic on the single
162 // interface may lead to processing the same message multiple times.
163 // We don't prohibit such configuration because raw sockets can as well
164 // handle the relayed traffic. We have to issue a warning, however, to
165 // draw administrator's attention.
166 if (can_use_bcast && multipleAddressesPerInterfaceActive()) {
167 LOG_WARN(dhcpsrv_logger, DHCPSRV_MULTIPLE_RAW_SOCKETS_PER_IFACE);
168 }
169 sopen = IfaceMgr::instance().openSockets4(port, can_use_bcast, error_callback);
170 } else {
171 // use_bcast is ignored for V6.
172 sopen = IfaceMgr::instance().openSockets6(port, error_callback);
173 }
174
175 if (!sopen) {
176 // If no socket were opened, log a warning because the server will
177 // not respond to any queries.
178 LOG_WARN(dhcpsrv_logger, DHCPSRV_NO_SOCKETS_OPEN);
179 }
180 }
181
182 void
reset()183 CfgIface::reset() {
184 wildcard_used_ = false;
185 iface_set_.clear();
186 address_map_.clear();
187 useSocketType(AF_INET, SOCKET_RAW);
188 }
189
190 void
setState(const uint16_t family,const bool inactive,const bool loopback_inactive) const191 CfgIface::setState(const uint16_t family, const bool inactive,
192 const bool loopback_inactive) const {
193 for (IfacePtr iface : IfaceMgr::instance().getIfaces()) {
194 bool iface_inactive = iface->flag_loopback_ ? loopback_inactive : inactive;
195 if (family == AF_INET) {
196 iface->inactive4_ = iface_inactive;
197 } else {
198 iface->inactive6_ = iface_inactive;
199 }
200
201 // Activate/deactivate all addresses.
202 setIfaceAddrsState(family, !inactive, *iface);
203 }
204 }
205
206 void
setIfaceAddrsState(const uint16_t family,const bool active,Iface & iface) const207 CfgIface::setIfaceAddrsState(const uint16_t family, const bool active,
208 Iface& iface) const {
209 // Activate/deactivate all addresses.
210 for (Iface::Address addr : iface.getAddresses()) {
211 if (addr.get().getFamily() == family) {
212 iface.setActive(addr.get(), active);
213 }
214 }
215 }
216
217 void
socketOpenErrorHandler(const std::string & errmsg)218 CfgIface::socketOpenErrorHandler(const std::string& errmsg) {
219 LOG_WARN(dhcpsrv_logger, DHCPSRV_OPEN_SOCKET_FAIL).arg(errmsg);
220 }
221
222 std::string
socketTypeToText() const223 CfgIface::socketTypeToText() const {
224 switch (socket_type_) {
225 case SOCKET_RAW:
226 return ("raw");
227
228 case SOCKET_UDP:
229 return ("udp");
230
231 default:
232 ;
233 }
234
235 isc_throw(Unexpected, "unsupported socket type " << socket_type_);
236 }
237
238 CfgIface::SocketType
textToSocketType(const std::string & socket_type_name) const239 CfgIface::textToSocketType(const std::string& socket_type_name) const {
240 if (socket_type_name == "udp") {
241 return (SOCKET_UDP);
242
243 } else if (socket_type_name == "raw") {
244 return (SOCKET_RAW);
245
246 } else {
247 isc_throw(InvalidSocketType, "unsupported socket type '"
248 << socket_type_name << "'");
249 }
250 }
251
252 CfgIface::OutboundIface
getOutboundIface() const253 CfgIface::getOutboundIface() const {
254 return (outbound_iface_);
255 }
256
257 std::string
outboundTypeToText() const258 CfgIface::outboundTypeToText() const {
259 switch (outbound_iface_) {
260 case SAME_AS_INBOUND:
261 return ("same-as-inbound");
262 case USE_ROUTING:
263 return ("use-routing");
264 default:
265 isc_throw(Unexpected, "unsupported outbound-type " << socket_type_);
266 }
267
268 }
269
270 CfgIface::OutboundIface
textToOutboundIface(const std::string & txt)271 CfgIface::textToOutboundIface(const std::string& txt) {
272 if (txt == "same-as-inbound") {
273 return (SAME_AS_INBOUND);
274
275 } else if (txt == "use-routing") {
276 return (USE_ROUTING);
277
278 } else {
279 isc_throw(BadValue, "unsupported outbound interface type '"
280 << txt << "'");
281 }
282 }
283
284 void
setOutboundIface(const OutboundIface & outbound_iface)285 CfgIface::setOutboundIface(const OutboundIface& outbound_iface) {
286 outbound_iface_ = outbound_iface;
287 }
288
289 void
use(const uint16_t family,const std::string & iface_name)290 CfgIface::use(const uint16_t family, const std::string& iface_name) {
291 // The interface name specified may have two formats:
292 // - "interface-name", e.g. eth0
293 // - "interface-name/address", e.g. eth0/10.0.0.1 or eth/2001:db8:1::1
294 // The latter format is used to open unicast socket on the specified
295 // interface. Here we are detecting which format was used and we strip
296 // all extraneous spaces.
297 size_t pos = iface_name.find("/");
298 std::string name;
299 std::string addr_str;
300 // There is no unicast address so the whole string is an interface name.
301 if (pos == std::string::npos) {
302 name = util::str::trim(iface_name);
303 if (name.empty()) {
304 isc_throw(InvalidIfaceName,
305 "empty interface name used in configuration");
306
307 } else if (name != ALL_IFACES_KEYWORD) {
308 if (IfaceMgr::instance().getIface(name) == NULL) {
309 isc_throw(NoSuchIface, "interface '" << name
310 << "' doesn't exist in the system");
311 }
312
313 } else if (wildcard_used_) {
314 isc_throw(DuplicateIfaceName, "the wildcard interface '"
315 << ALL_IFACES_KEYWORD << "' can only be specified once");
316
317 } else {
318 LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
319 DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE);
320 wildcard_used_ = true;
321
322 }
323
324 } else {
325 // The interface name includes the address on which the socket should
326 // be opened, and we need to split interface name and the address to
327 // two variables.
328 name = util::str::trim(iface_name.substr(0, pos));
329 addr_str = util::str::trim(iface_name.substr(pos + 1));
330
331 // Interface name must not be empty.
332 if (name.empty()) {
333 isc_throw(InvalidIfaceName,
334 "empty interface name specified in the"
335 " interface configuration");
336
337 }
338 // An address following the interface name must not be empty.
339 if (addr_str.empty()) {
340 isc_throw(InvalidIfaceName,
341 "empty address specified in the interface"
342 << " configuration");
343
344 }
345
346 // Interface name must not be the wildcard name.
347 if (name == ALL_IFACES_KEYWORD) {
348 isc_throw(InvalidIfaceName,
349 "wildcard interface name '" << ALL_IFACES_KEYWORD
350 << "' must not be used in conjunction with an"
351 " address");
352
353 }
354
355 // Interface must exist.
356 IfacePtr iface = IfaceMgr::instance().getIface(name);
357 if (!iface) {
358 isc_throw(NoSuchIface, "interface '" << name
359 << "' doesn't exist in the system");
360
361 }
362
363 // Convert address string. This may throw an exception if the address
364 // is invalid.
365 IOAddress addr(addr_str);
366
367 // Validate V6 address.
368 if (family == AF_INET6) {
369 // Check that the address is a valid unicast address.
370 if (!addr.isV6() || addr.isV6Multicast()) {
371 isc_throw(InvalidIfaceName, "address '" << addr << "' is not"
372 " a valid IPv6 unicast address");
373 }
374
375 // There are valid cases where link local address can be specified to
376 // receive unicast traffic, e.g. sent by relay agent.
377 if (addr.isV6LinkLocal()) {
378 LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL)
379 .arg(addr.toText()).arg(name);
380 }
381
382 } else {
383 if (!addr.isV4()) {
384 isc_throw(InvalidIfaceName, "address '" << addr << "' is not"
385 " a valid IPv4 address");
386 }
387 }
388
389 // Interface must have this address assigned.
390 if (!iface->hasAddress(addr)) {
391 isc_throw(NoSuchAddress,
392 "interface '" << name << "' doesn't have address '"
393 << addr << "' assigned");
394 }
395
396 // For the IPv4, if the interface name was specified (instead of the interface-
397 // address tuple) all addresses are already activated. Adding an explicit address
398 // for the interface should result in error.
399 if ((family == AF_INET) && (iface_set_.find(iface->getName()) != iface_set_.end())) {
400 isc_throw(DuplicateIfaceName, "interface '" << iface->getName()
401 << "' has already been selected");
402 }
403
404 // Check if the address hasn't been selected already.
405 std::pair<const std::string, IOAddress> iface_address_tuple(name, addr);
406 if (std::find(address_map_.begin(), address_map_.end(),
407 iface_address_tuple) != address_map_.end()) {
408 isc_throw(DuplicateAddress, "must not select address '"
409 << addr << "' for interface '" << name << "' "
410 "because this address is already selected");
411 }
412
413 if (family == AF_INET6) {
414 LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_USE_UNICAST)
415 .arg(addr.toText()).arg(name);
416
417 } else {
418 LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_USE_ADDRESS)
419 .arg(addr.toText()).arg(name);
420 }
421 address_map_.insert(std::pair<std::string, IOAddress>(name, addr));
422 }
423
424 // If interface name was explicitly specified without an address, we will
425 // insert the interface name to the set of enabled interfaces.
426 if ((name != ALL_IFACES_KEYWORD) && addr_str.empty()) {
427 // An interface has been selected or an IPv4 address on this interface
428 // has been selected it is not allowed to select the whole interface.
429 if ((iface_set_.find(name) != iface_set_.end()) ||
430 ((family == AF_INET) && address_map_.count(name) > 0)) {
431 isc_throw(DuplicateIfaceName, "interface '" << name
432 << "' has already been specified");
433 }
434
435 // Log that we're listening on the specific interface and that the
436 // address is not explicitly specified.
437 if (addr_str.empty()) {
438 LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_ADD_IFACE).arg(name);
439 }
440 iface_set_.insert(name);
441 }
442 }
443
444 void
useSocketType(const uint16_t family,const SocketType & socket_type)445 CfgIface::useSocketType(const uint16_t family,
446 const SocketType& socket_type) {
447 if (family != AF_INET) {
448 isc_throw(InvalidSocketType, "socket type must not be specified for"
449 " the DHCPv6 server");
450 }
451 socket_type_ = socket_type;
452 LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_SOCKET_TYPE_SELECT)
453 .arg(socketTypeToText());
454 }
455
456 void
useSocketType(const uint16_t family,const std::string & socket_type_name)457 CfgIface::useSocketType(const uint16_t family,
458 const std::string& socket_type_name) {
459 useSocketType(family, textToSocketType(socket_type_name));
460 }
461
462 ElementPtr
toElement() const463 CfgIface::toElement() const {
464 ElementPtr result = Element::createMap();
465
466 // Set user context
467 contextToElement(result);
468
469 // Set interfaces
470 ElementPtr ifaces = Element::createList();
471 if (wildcard_used_) {
472 ifaces->add(Element::create(std::string(ALL_IFACES_KEYWORD)));
473 }
474 for (IfaceSet::const_iterator iface = iface_set_.cbegin();
475 iface != iface_set_.cend(); ++iface) {
476 ifaces->add(Element::create(*iface));
477 }
478 for (ExplicitAddressMap::const_iterator address = address_map_.cbegin();
479 address != address_map_.cend(); ++address) {
480 std::string spec = address->first + "/" + address->second.toText();
481 ifaces->add(Element::create(spec));
482 }
483 result->set("interfaces", ifaces);
484
485 // Set dhcp-socket-type (no default because it is DHCPv4 specific)
486 // @todo emit raw if and only if DHCPv4
487 if (socket_type_ != SOCKET_RAW) {
488 result->set("dhcp-socket-type", Element::create(std::string("udp")));
489 }
490
491 if (outbound_iface_ != SAME_AS_INBOUND) {
492 result->set("outbound-interface", Element::create(outboundTypeToText()));
493 }
494
495 // Set re-detect
496 result->set("re-detect", Element::create(re_detect_));
497
498 return (result);
499 }
500
501 } // end of isc::dhcp namespace
502 } // end of isc namespace
503