Kea: iface_mgr_linux.cc Source File

| Kea 3.2.0-git

iface_mgr_linux.cc
Go to the documentation of this file.
1// Copyright (C) 2011-2026 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
21
22#include <config.h>
23
24#if defined(OS_LINUX)
25
26#include <asiolink/io_address.h>
27#include <dhcp/iface_mgr.h>
28#include <dhcp/iface_mgr_error_handler.h>
29#include <dhcp/pkt_filter_inet.h>
30#include <dhcp/pkt_filter_lpf.h>
31#include <exceptions/exceptions.h>
32#include <util/io/sockaddr_util.h>
33
34#include <boost/array.hpp>
35
36#include <fcntl.h>
37#include <stdint.h>
38#include <net/if.h>
39#include <linux/rtnetlink.h>
40
41using namespace std;
42using namespace isc;
43using namespace isc::asiolink;
44using namespace isc::dhcp;
45using namespace isc::util::io::internal;
46
47static_assert(IFLA_MAX>=IFA_MAX, "IFLA_MAX>=IFA_MAX");
48
49namespace {
50
55class Netlink
56{
57public:
58
70 typedef vector<nlmsghdr*> NetlinkMessages;
71
85 typedef boost::array<struct rtattr*, IFLA_MAX + 1> RTattribPtrs;
86
87 Netlink() : fd_(-1), seq_(0), dump_(0) {
88 memset(&local_, 0, sizeof(struct sockaddr_nl));
89 memset(&peer_, 0, sizeof(struct sockaddr_nl));
90 }
91
92 ~Netlink() {
93 rtnl_close_socket();
94 }
95
96
97 void rtnl_open_socket();
98 void rtnl_send_request(int family, int type);
99 void rtnl_store_reply(NetlinkMessages& storage, const nlmsghdr* msg);
100 void parse_rtattr(RTattribPtrs& table, rtattr* rta, int len);
101 void ipaddrs_get(Iface& iface, NetlinkMessages& addr_info);
102 void rtnl_process_reply(NetlinkMessages& info);
103 void release_list(NetlinkMessages& messages);
104 void rtnl_close_socket();
105
106private:
107 int fd_; // Netlink file descriptor
108 sockaddr_nl local_; // Local addresses
109 sockaddr_nl peer_; // Remote address
110 uint32_t seq_; // Counter used for generating unique sequence numbers
111 uint32_t dump_; // Number of expected message response
112};
113
115const static size_t SNDBUF_SIZE = 32768;
116
118const static size_t RCVBUF_SIZE = 32768;
119
123void Netlink::rtnl_open_socket() {
124
125 fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
126 if (fd_ < 0) {
127 isc_throw(Unexpected, "Failed to create NETLINK socket.");
128 }
129
130 if (fcntl(fd_, F_SETFD, FD_CLOEXEC) < 0) {
131 isc_throw(Unexpected, "Failed to set close-on-exec in NETLINK socket.");
132 }
133
134 if (setsockopt(fd_, SOL_SOCKET, SO_SNDBUF, &SNDBUF_SIZE, sizeof(SNDBUF_SIZE)) < 0) {
135 isc_throw(Unexpected, "Failed to set send buffer in NETLINK socket.");
136 }
137
138 if (setsockopt(fd_, SOL_SOCKET, SO_RCVBUF, &RCVBUF_SIZE, sizeof(RCVBUF_SIZE)) < 0) {
139 isc_throw(Unexpected, "Failed to set receive buffer in NETLINK socket.");
140 }
141
142 local_.nl_family = AF_NETLINK;
143 local_.nl_groups = 0;
144
145 if (::bind(fd_, convertSockAddr(&local_), sizeof(local_)) < 0) {
146 isc_throw(Unexpected, "Failed to bind netlink socket.");
147 }
148
149 socklen_t addr_len = sizeof(local_);
150 if (getsockname(fd_, convertSockAddr(&local_), &addr_len) < 0) {
151 isc_throw(Unexpected, "Getsockname for netlink socket failed.");
152 }
153
154 // just 2 sanity checks and we are done
155 if ( (addr_len != sizeof(local_)) ||
156 (local_.nl_family != AF_NETLINK) ) {
157 isc_throw(Unexpected, "getsockname() returned unexpected data for netlink socket.");
158 }
159}
160
162void Netlink::rtnl_close_socket() {
163 if (fd_ != -1) {
164 close(fd_);
165 }
166 fd_ = -1;
167}
168
173void Netlink::rtnl_send_request(int family, int type) {
174 struct Req {
175 nlmsghdr netlink_header;
176 rtgenmsg generic;
177 };
178 Req req; // we need this type named for offsetof() used in assert
179 struct sockaddr_nl nladdr;
180
181 // do a sanity check. Verify that Req structure is aligned properly
182 static_assert(sizeof(nlmsghdr) == offsetof(Req, generic),
183 "sizeof(nlmsghdr) == offsetof(Req, generic)");
184
185 memset(&nladdr, 0, sizeof(nladdr));
186 nladdr.nl_family = AF_NETLINK;
187
188 // According to netlink(7) manpage, mlmsg_seq must be set to a sequence
189 // number and is used to track messages. That is just a value that is
190 // opaque to kernel, and user-space code is supposed to use it to match
191 // incoming responses to sent requests. That is not really useful as we
192 // send a single request and get a single response at a time. However, we
193 // obey the man page suggestion and just set this to monotonically
194 // increasing numbers.
195 seq_++;
196
197 // This will be used to finding correct response (responses
198 // sent by kernel are supposed to have the same sequence number
199 // as the request we sent).
200 dump_ = seq_;
201
202 memset(&req, 0, sizeof(req));
203 req.netlink_header.nlmsg_len = sizeof(req);
204 req.netlink_header.nlmsg_type = type;
205 req.netlink_header.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
206 req.netlink_header.nlmsg_pid = 0;
207 req.netlink_header.nlmsg_seq = seq_;
208 req.generic.rtgen_family = family;
209
210 int status = sendto(fd_, static_cast<void*>(&req), sizeof(req), 0,
211 static_cast<struct sockaddr*>(static_cast<void*>(&nladdr)),
212 sizeof(nladdr));
213
214 if (status<0) {
215 isc_throw(Unexpected, "Failed to send " << sizeof(nladdr)
216 << " bytes over netlink socket.");
217 }
218}
219
228void Netlink::rtnl_store_reply(NetlinkMessages& storage, const struct nlmsghdr *msg) {
229 // we need to make a copy of this message. We really can't allocate
230 // nlmsghdr directly as it is only part of the structure. There are
231 // many message types with varying lengths and a common header.
232 struct nlmsghdr* copy = reinterpret_cast<struct nlmsghdr*>(new char[msg->nlmsg_len]);
233 memcpy(copy, msg, msg->nlmsg_len);
234
235 // push_back copies only pointer content, not the pointed-to object.
236 storage.push_back(copy);
237}
238
249void Netlink::parse_rtattr(RTattribPtrs& table, struct rtattr* rta, int len) {
250 std::fill(table.begin(), table.end(), static_cast<struct rtattr*>(NULL));
251 // RTA_OK and RTA_NEXT() are macros defined in linux/rtnetlink.h
252 // they are used to handle rtattributes. RTA_OK checks if the structure
253 // pointed by rta is reasonable and passes all sanity checks.
254 // RTA_NEXT() returns pointer to the next rtattr structure that
255 // immediately follows pointed rta structure. See aforementioned
256 // header for details.
257 while (RTA_OK(rta, len)) {
258 if (rta->rta_type < table.size()) {
259 table[rta->rta_type] = rta;
260 }
261 rta = RTA_NEXT(rta,len);
262 }
263 if (len) {
264 isc_throw(Unexpected, "Failed to parse RTATTR in netlink message.");
265 }
266}
267
278void Netlink::ipaddrs_get(Iface& iface, NetlinkMessages& addr_info) {
279 uint8_t addr[V6ADDRESS_LEN];
280 RTattribPtrs rta_tb;
281
282 for (auto const& msg : addr_info) {
283 ifaddrmsg* ifa = static_cast<ifaddrmsg*>(NLMSG_DATA(msg));
284
285 // These are not the addresses you are looking for
286 if (ifa->ifa_index != iface.getIndex()) {
287 continue;
288 }
289
290 if ((ifa->ifa_family == AF_INET6) || (ifa->ifa_family == AF_INET)) {
291 std::fill(rta_tb.begin(), rta_tb.end(), static_cast<rtattr*>(NULL));
292 parse_rtattr(rta_tb, IFA_RTA(ifa), msg->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
293 if (!rta_tb[IFA_LOCAL]) {
294 rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS];
295 }
296 if (!rta_tb[IFA_ADDRESS]) {
297 rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL];
298 }
299
300 memcpy(addr, RTA_DATA(rta_tb[IFLA_ADDRESS]),
301 ifa->ifa_family==AF_INET?V4ADDRESS_LEN:V6ADDRESS_LEN);
302 IOAddress a = IOAddress::fromBytes(ifa->ifa_family, addr);
303 iface.addAddress(a);
304
306 }
307 }
308}
309
319void Netlink::rtnl_process_reply(NetlinkMessages& info) {
320 sockaddr_nl nladdr;
321 iovec iov;
322 msghdr msg;
323 memset(&msg, 0, sizeof(msghdr));
324 msg.msg_name = &nladdr;
325 msg.msg_namelen = sizeof(nladdr);
326 msg.msg_iov = &iov;
327 msg.msg_iovlen = 1;
328
329 char buf[RCVBUF_SIZE];
330
331 iov.iov_base = buf;
332 iov.iov_len = sizeof(buf);
333 while (true) {
334 int status = recvmsg(fd_, &msg, 0);
335
336 if (status < 0) {
337 if (errno == EINTR) {
338 continue;
339 }
340 isc_throw(Unexpected, "Error " << errno
341 << " while processing reply from netlink socket.");
342 }
343
344 if (status == 0) {
345 isc_throw(Unexpected, "EOF while reading netlink socket.");
346 }
347
348 nlmsghdr* header = static_cast<nlmsghdr*>(static_cast<void*>(buf));
349 while (NLMSG_OK(header, static_cast<unsigned int>(status))) {
350
351 // Received a message not addressed to our process, or not
352 // with a sequence number we are expecting. Ignore, and
353 // look at the next one.
354 if (nladdr.nl_pid != 0 ||
355 header->nlmsg_pid != local_.nl_pid ||
356 header->nlmsg_seq != dump_) {
357 header = NLMSG_NEXT(header, status);
358 continue;
359 }
360
361 if (header->nlmsg_type == NLMSG_DONE) {
362 // End of message.
363 return;
364 }
365
366 if (header->nlmsg_type == NLMSG_ERROR) {
367 nlmsgerr* err = static_cast<nlmsgerr*>(NLMSG_DATA(header));
368 if (header->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
369 // We are really out of luck here. We can't even say what is
370 // wrong as error message is truncated. D'oh.
371 isc_throw(Unexpected, "Netlink reply read failed.");
372 } else {
373 isc_throw(Unexpected, "Netlink reply read error " << -err->error);
374 }
375 // Never happens we throw before we reach here
376 return;
377 }
378
379 // store the data
380 rtnl_store_reply(info, header);
381
382 header = NLMSG_NEXT(header, status);
383 }
384 if (msg.msg_flags & MSG_TRUNC) {
385 isc_throw(Unexpected, "Message received over netlink truncated.");
386 }
387 if (status) {
388 isc_throw(Unexpected, "Trailing garbage of " << status << " bytes received over netlink.");
389 }
390 }
391}
392
396void Netlink::release_list(NetlinkMessages& messages) {
397 // let's free local copies of stored messages
398 for (auto const& msg : messages) {
399 delete[] msg;
400 }
401
402 // and get rid of the message pointers as well
403 messages.clear();
404}
405
406} // end of anonymous namespace
407
408namespace isc {
409namespace dhcp {
410
415void IfaceMgr::detectIfaces(bool update_only) {
416 if (detect_callback_) {
417 if (!detect_callback_(update_only)) {
418 return;
419 }
420 }
421
422 // Copies of netlink messages about links will be stored here.
423 Netlink::NetlinkMessages link_info;
424
425 // Copies of netlink messages about addresses will be stored here.
426 Netlink::NetlinkMessages addr_info;
427
428 // Socket descriptors and other rtnl-related parameters.
429 Netlink nl;
430
431 // Table with pointers to address attributes.
432 Netlink::RTattribPtrs attribs_table;
433 std::fill(attribs_table.begin(), attribs_table.end(),
434 static_cast<struct rtattr*>(NULL));
435
436 // Open socket
437 nl.rtnl_open_socket();
438
439 // Now we have open functional socket, let's use it!
440 // Ask for list of network interfaces...
441 nl.rtnl_send_request(AF_PACKET, RTM_GETLINK);
442
443 // Get reply and store it in link_info list:
444 // response is received as with any other socket - just a series
445 // of bytes. They are representing collection of netlink messages
446 // concatenated together. rtnl_process_reply will parse this
447 // buffer, copy each message to a newly allocated memory and
448 // store pointers to it in link_info. This allocated memory will
449 // be released later. See release_info(link_info) below.
450 nl.rtnl_process_reply(link_info);
451
452 // Now ask for list of addresses (AF_UNSPEC = of any family)
453 // Let's repeat, but this time ask for any addresses.
454 // That includes IPv4, IPv6 and any other address families that
455 // are happen to be supported by this system.
456 nl.rtnl_send_request(AF_UNSPEC, RTM_GETADDR);
457
458 // Get reply and store it in addr_info list.
459 // Again, we will allocate new memory and store messages in
460 // addr_info. It will be released later using release_info(addr_info).
461 nl.rtnl_process_reply(addr_info);
462
463 // Now build list with interface names
464 for (auto const& msg : link_info) {
465 // Required to display information about interface
466 struct ifinfomsg* interface_info = static_cast<ifinfomsg*>(NLMSG_DATA(msg));
467 int len = msg->nlmsg_len;
468 len -= NLMSG_LENGTH(sizeof(*interface_info));
469 nl.parse_rtattr(attribs_table, IFLA_RTA(interface_info), len);
470
471 // valgrind reports *possible* memory leak in the line below, but it is
472 // bogus. Nevertheless, the whole interface definition has been split
473 // into three separate steps for easier debugging.
474 const char* tmp = static_cast<const char*>(RTA_DATA(attribs_table[IFLA_IFNAME]));
475 string iface_name(tmp); // <--- bogus valgrind warning here
476 // This is guaranteed both by the if_nametoindex() implementation
477 // and by kernel dev_new_index() code. In fact 0 is impossible too...
478 if (interface_info->ifi_index < 0) {
479 isc_throw(OutOfRange, "negative interface index");
480 }
481 IfacePtr iface;
482 bool created = true;
483
484 if (update_only) {
485 iface = getIface(iface_name);
486 if (iface) {
487 created = false;
488 }
489 }
490
491 if (!iface) {
492 iface.reset(new Iface(iface_name, interface_info->ifi_index));
493 }
494
495 iface->setHWType(interface_info->ifi_type);
496 iface->setFlags(interface_info->ifi_flags);
497
498 // Does interface have LL_ADDR?
499 if (attribs_table[IFLA_ADDRESS]) {
500 iface->setMac(static_cast<const uint8_t*>(RTA_DATA(attribs_table[IFLA_ADDRESS])),
501 RTA_PAYLOAD(attribs_table[IFLA_ADDRESS]));
502 } else {
503 // Tunnels can have no LL_ADDR. RTA_PAYLOAD doesn't check it and
504 // try to dereference it in this manner
505 }
506
507 nl.ipaddrs_get(*iface, addr_info);
508
509 // addInterface can now throw so protect against memory leaks.
510 try {
511 if (created) {
512 addInterface(iface);
513 }
514 } catch (...) {
515 nl.release_list(link_info);
516 nl.release_list(addr_info);
517 throw;
518 }
519 }
520
521 nl.release_list(link_info);
522 nl.release_list(addr_info);
523}
524
531void Iface::setFlags(uint64_t flags) {
532 flags_ = flags;
533
534 flag_loopback_ = flags & IFF_LOOPBACK;
535 flag_up_ = flags & IFF_UP;
536 flag_running_ = flags & IFF_RUNNING;
537 flag_multicast_ = flags & IFF_MULTICAST;
538 flag_broadcast_ = flags & IFF_BROADCAST;
539}
540
541void
542 IfaceMgr::setMatchingPacketFilter(const bool direct_response_desired) {
543 if (direct_response_desired) {
544 setPacketFilter(PktFilterPtr(new PktFilterLPF()));
545
546 } else {
547 setPacketFilter(PktFilterPtr(new PktFilterInet()));
548
549 }
550}
551
552bool
553IfaceMgr::openMulticastSocket(Iface& iface,
554 const isc::asiolink::IOAddress& addr,
555 const uint16_t port,
556 IfaceMgrErrorMsgCallback error_handler) {
557 // This variable will hold a descriptor of the socket bound to
558 // link-local address. It may be required for us to close this
559 // socket if an attempt to open and bind a socket to multicast
560 // address fails.
561 int sock;
562 try {
563 sock = openSocket(iface.getName(), addr, port, iface.flag_multicast_);
564
565 } catch (const Exception& ex) {
566 IFACEMGR_ERROR(SocketConfigError, error_handler, IfacePtr(),
567 "Failed to open link-local socket on "
568 "interface " << iface.getName() << ": "
569 << ex.what());
570 return (false);
571
572 }
573
574 // In order to receive multicast traffic another socket is opened
575 // and bound to the multicast address.
576
583 if (iface.flag_multicast_) {
584 int sockm = -1;
585 try {
586 sockm = openSocket(iface.getName(),
587 IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
588 port);
589 openSocket(iface.getName(), IOAddress(ALL_DHCP_SERVERS), port);
590 } catch (const Exception& ex) {
591 // An attempt to open and bind a socket to multicast address
592 // has failed. We have to close the socket we previously
593 // bound to link-local address - this is everything or
594 // nothing strategy.
595 iface.delSocket(sock);
596 if (sockm != -1) {
597 iface.delSocket(sockm);
598 }
599 IFACEMGR_ERROR(SocketConfigError, error_handler, IfacePtr(),
600 "Failed to open multicast socket on"
601 " interface " << iface.getName()
602 << ", reason: " << ex.what());
603 return (false);
604 }
605 }
606 // Both sockets have opened successfully.
607 return (true);
608}
609
610int
611 IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port,
612 const bool join_multicast) {
613 // Assuming that packet filter is not NULL, because its modifier checks it.
614 SocketInfo info = packet_filter6_->openSocket(iface, addr, port,
615 join_multicast);
616 iface.addSocket(info);
617
618 return (info.sockfd_);
619}
620
621} // end of isc::dhcp namespace
622} // end of isc namespace
623
624#endif // if defined(LINUX)
Exception
This is a base class for exceptions thrown from the DNS library module.
Definition exceptions/exceptions.h:23
Exception::what
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
Definition exceptions/exceptions.cc:32
isc::OutOfRange
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
Definition exceptions/exceptions.h:115
isc::Unexpected
A generic exception that is thrown when an unexpected error condition occurs.
Definition exceptions/exceptions.h:153
isc::asiolink::IOAddress
The IOAddress class represents an IP addresses (version agnostic).
Definition io_address.h:53
isc::asiolink::IOAddress::fromBytes
static IOAddress fromBytes(short family, const uint8_t *data)
Creates an address from over wire data.
Definition io_address.cc:65
isc::dhcp::IfaceMgr::detectIfaces
void detectIfaces(bool update_only=false)
Detects network interfaces.
isc::dhcp::IfaceMgr::openSocket6
int openSocket6(Iface &iface, const isc::asiolink::IOAddress &addr, uint16_t port, const bool join_multicast)
Opens IPv6 socket.
isc::dhcp::IfaceMgr::setMatchingPacketFilter
void setMatchingPacketFilter(const bool direct_response_desired=false)
Set Packet Filter object to handle send/receive packets.
isc::dhcp::Iface
Represents a single network interface.
Definition iface_mgr.h:136
isc::dhcp::Iface::flag_multicast_
bool flag_multicast_
Flag specifies if selected interface is multicast capable.
Definition iface_mgr.h:519
isc::dhcp::Iface::getName
std::string getName() const
Returns interface name.
Definition iface_mgr.h:264
isc::dhcp::Iface::setFlags
void setFlags(uint64_t flags)
Sets flag_*_ fields based on bitmask value returned by OS.
isc::dhcp::Iface::getIndex
unsigned int getIndex() const
Returns interface index.
Definition iface_mgr.h:257
isc::dhcp::Iface::delSocket
bool delSocket(uint16_t sockfd)
Closes socket.
Definition iface_mgr.cc:164
isc::dhcp::Iface::addAddress
void addAddress(const isc::asiolink::IOAddress &addr)
Adds an address to an interface.
Definition iface_mgr.cc:255
isc::dhcp::Iface::addSocket
void addSocket(const SocketInfo &sock)
Adds socket descriptor to an interface.
Definition iface_mgr.h:369
isc::dhcp::Iface::setHWType
void setHWType(uint16_t type)
Sets up hardware type of the interface.
Definition iface_mgr.h:271
isc::dhcp::PktFilterInet
Packet handling class using AF_INET socket family.
Definition pkt_filter_inet.h:20
isc::dhcp::PktFilterLPF
Packet handling class using Linux Packet Filtering.
Definition pkt_filter_lpf.h:23
isc::dhcp::SocketConfigError
IfaceMgr exception thrown thrown when socket opening or configuration failed.
Definition iface_mgr.h:66
ALL_DHCP_SERVERS
#define ALL_DHCP_SERVERS
Definition dhcp6.h:298
ALL_DHCP_RELAY_AGENTS_AND_SERVERS
#define ALL_DHCP_RELAY_AGENTS_AND_SERVERS
Definition dhcp6.h:297
exceptions.h
isc_throw
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Definition exceptions/exceptions.h:210
iface_mgr.h
iface_mgr_error_handler.h
IFACEMGR_ERROR
#define IFACEMGR_ERROR(ex_type, handler, iface, stream)
A macro which handles an error in IfaceMgr.
Definition iface_mgr_error_handler.h:34
io_address.h
isc::asiolink
Definition addr_utilities.cc:170
isc::data::copy
ElementPtr copy(ConstElementPtr from, unsigned level)
Copy the data up to a nesting level.
Definition data.cc:1522
isc::db::info
@ info
Definition db_log.h:126
isc::dhcp
Definition dhcp4/client_handler.cc:20
isc::dhcp::PktFilterPtr
boost::shared_ptr< PktFilter > PktFilterPtr
Pointer to a PktFilter object.
Definition pkt_filter.h:144
isc::dhcp::IfacePtr
boost::shared_ptr< Iface > IfacePtr
Type definition for the pointer to an Iface object.
Definition iface_mgr.h:555
isc::dhcp::IfaceMgrErrorMsgCallback
std::function< void(const std::string &errmsg)> IfaceMgrErrorMsgCallback
This type describes the callback function invoked when error occurs in the IfaceMgr.
Definition iface_mgr.h:732
isc::util::io::internal
Definition pktinfo_utilities.h:20
isc::util::io::internal::convertSockAddr
const struct sockaddr * convertSockAddr(const SAType *sa)
Definition sockaddr_util.h:41
isc
Defines the logger used by the top-level component of kea-lfc.
Definition check_exists_add.cc:18
pkt_filter_inet.h
pkt_filter_lpf.h
sockaddr_util.h
isc::dhcp::SocketInfo
Holds information about socket.
Definition socket_info.h:18