blob: b564afeaa782c9b4f0e5a80d0965bd4890cc879b [file] [log] [blame]
Davide Pesaventoa0b2a2c2018-07-19 01:01:06 -04001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/*
3 * Copyright (c) 2013-2018 Regents of the University of California.
4 *
5 * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
6 *
7 * ndn-cxx library is free software: you can redistribute it and/or modify it under the
8 * terms of the GNU Lesser General Public License as published by the Free Software
9 * Foundation, either version 3 of the License, or (at your option) any later version.
10 *
11 * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
14 *
15 * You should have received copies of the GNU General Public License and GNU Lesser
16 * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
17 * <http://www.gnu.org/licenses/>.
18 *
19 * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
20 *
21 * @author Davide Pesavento <davide.pesavento@lip6.fr>
22 */
23
24#include "netlink-socket.hpp"
25#include "netlink-message.hpp"
26#include "../../util/logger.hpp"
27#include "../../util/time.hpp"
28
29#include <cerrno>
30#include <cstring>
31#include <sys/socket.h>
32
33#include <boost/asio/write.hpp>
34
35#ifndef SOL_NETLINK
36#define SOL_NETLINK 270
37#endif
38#ifndef RTEXT_FILTER_SKIP_STATS
39#define RTEXT_FILTER_SKIP_STATS (1 << 3)
40#endif
41
42NDN_LOG_INIT(ndn.NetworkMonitor);
43
44namespace ndn {
45namespace net {
46
47NetlinkSocket::NetlinkSocket(boost::asio::io_service& io)
48 : m_sock(make_shared<boost::asio::posix::stream_descriptor>(io))
49 , m_pid(0)
50 , m_seqNum(static_cast<uint32_t>(time::system_clock::now().time_since_epoch().count()))
51 , m_buffer(16 * 1024) // 16 KiB
52{
53}
54
55NetlinkSocket::~NetlinkSocket()
56{
57 boost::system::error_code ec;
58 m_sock->close(ec);
59}
60
61void
62NetlinkSocket::open(int protocol)
63{
64 int fd = ::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
65 if (fd < 0) {
66 BOOST_THROW_EXCEPTION(Error("Cannot create netlink socket ("s + std::strerror(errno) + ")"));
67 }
68 m_sock->assign(fd);
69
70 // increase socket receive buffer to 1MB to avoid losing messages
71 const int bufsize = 1 * 1024 * 1024;
72 if (::setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) < 0) {
73 // not a fatal error
74 NDN_LOG_DEBUG("setting SO_RCVBUF failed: " << std::strerror(errno));
75 }
76
77 // enable control messages for received packets to get the destination group
78 const int one = 1;
79 if (::setsockopt(fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one)) < 0) {
80 BOOST_THROW_EXCEPTION(Error("Cannot enable NETLINK_PKTINFO ("s + std::strerror(errno) + ")"));
81 }
82
83 sockaddr_nl addr{};
84 addr.nl_family = AF_NETLINK;
85 if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
86 BOOST_THROW_EXCEPTION(Error("Cannot bind netlink socket ("s + std::strerror(errno) + ")"));
87 }
88
89 // find out what pid has been assigned to us
90 socklen_t len = sizeof(addr);
91 if (::getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &len) < 0) {
92 BOOST_THROW_EXCEPTION(Error("Cannot obtain netlink socket address ("s + std::strerror(errno) + ")"));
93 }
94 if (len != sizeof(addr)) {
95 BOOST_THROW_EXCEPTION(Error("Wrong address length (" + to_string(len) + ")"));
96 }
97 if (addr.nl_family != AF_NETLINK) {
98 BOOST_THROW_EXCEPTION(Error("Wrong address family (" + to_string(addr.nl_family) + ")"));
99 }
100 m_pid = addr.nl_pid;
101 NDN_LOG_TRACE("our pid is " << m_pid);
102
103#ifdef NDN_CXX_HAVE_NETLINK_EXT_ACK
104 // enable extended ACK reporting
105 if (::setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0) {
106 // not a fatal error
107 NDN_LOG_DEBUG("setting NETLINK_EXT_ACK failed: " << std::strerror(errno));
108 }
109#endif // NDN_CXX_HAVE_NETLINK_EXT_ACK
110}
111
112void
113NetlinkSocket::joinGroup(int group)
114{
115 if (::setsockopt(m_sock->native_handle(), SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
116 &group, sizeof(group)) < 0) {
117 BOOST_THROW_EXCEPTION(Error("Cannot join netlink group " + to_string(group) +
118 " (" + std::strerror(errno) + ")"));
119 }
120}
121
122void
123NetlinkSocket::startAsyncReceive(MessageCallback cb)
124{
125 BOOST_ASSERT(cb != nullptr);
126 BOOST_ASSERT(m_onMessage == nullptr);
127
128 m_onMessage = std::move(cb);
129 asyncWait();
130}
131
132static const char*
133nlmsgTypeToString(uint16_t type)
134{
135#define NLMSG_STRINGIFY(x) case NLMSG_##x: return "<" #x ">"
136#define RTM_STRINGIFY(x) case RTM_##x: return "<" #x ">"
137 switch (type) {
138 NLMSG_STRINGIFY(NOOP);
139 NLMSG_STRINGIFY(ERROR);
140 NLMSG_STRINGIFY(DONE);
141 NLMSG_STRINGIFY(OVERRUN);
142 RTM_STRINGIFY(NEWLINK);
143 RTM_STRINGIFY(DELLINK);
144 RTM_STRINGIFY(GETLINK);
145 RTM_STRINGIFY(NEWADDR);
146 RTM_STRINGIFY(DELADDR);
147 RTM_STRINGIFY(GETADDR);
148 RTM_STRINGIFY(NEWROUTE);
149 RTM_STRINGIFY(DELROUTE);
150 RTM_STRINGIFY(GETROUTE);
151 default:
152 return "";
153 }
154#undef NLMSG_STRINGIFY
155#undef RTM_STRINGIFY
156}
157
158void
159NetlinkSocket::asyncWait()
160{
161 m_sock->async_read_some(boost::asio::null_buffers(),
162 // capture a copy of 'm_sock' to prevent its deallocation while the handler is still pending
163 [this, sock = m_sock] (const boost::system::error_code& ec, size_t) {
164 if (!sock->is_open() || ec == boost::asio::error::operation_aborted) {
165 // socket was closed, ignore the error
166 NDN_LOG_DEBUG("netlink socket closed or operation aborted");
167 }
168 else if (ec) {
169 NDN_LOG_ERROR("read failed: " << ec.message());
170 BOOST_THROW_EXCEPTION(Error("Netlink socket read error (" + ec.message() + ")"));
171 }
172 else {
173 receiveAndValidate();
174 asyncWait();
175 }
176 });
177}
178
179void
180NetlinkSocket::receiveAndValidate()
181{
182 sockaddr_nl sender{};
183 iovec iov{};
184 iov.iov_base = m_buffer.data();
185 iov.iov_len = m_buffer.size();
186 uint8_t cmsgBuffer[CMSG_SPACE(sizeof(nl_pktinfo))];
187 msghdr msg{};
188 msg.msg_name = &sender;
189 msg.msg_namelen = sizeof(sender);
190 msg.msg_iov = &iov;
191 msg.msg_iovlen = 1;
192 msg.msg_control = cmsgBuffer;
193 msg.msg_controllen = sizeof(cmsgBuffer);
194
195 ssize_t nBytesRead = ::recvmsg(m_sock->native_handle(), &msg, 0);
196 if (nBytesRead < 0) {
197 std::string errorString = std::strerror(errno);
198 if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
199 // not a fatal error
200 NDN_LOG_DEBUG("recvmsg failed: " << errorString);
201 return;
202 }
203 NDN_LOG_ERROR("recvmsg failed: " << errorString);
204 BOOST_THROW_EXCEPTION(Error("Netlink socket receive error (" + errorString + ")"));
205 }
206
207 NDN_LOG_TRACE("read " << nBytesRead << " bytes from netlink socket");
208
209 if (msg.msg_flags & MSG_TRUNC) {
210 NDN_LOG_ERROR("truncated message");
211 BOOST_THROW_EXCEPTION(Error("Received truncated netlink message"));
212 // TODO: grow the buffer and start over
213 }
214
215 if (msg.msg_namelen >= sizeof(sender) && sender.nl_pid != 0) {
216 NDN_LOG_TRACE("ignoring message from pid=" << sender.nl_pid);
217 return;
218 }
219
220 uint32_t nlGroup = 0;
221 for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
222 if (cmsg->cmsg_level == SOL_NETLINK &&
223 cmsg->cmsg_type == NETLINK_PKTINFO &&
224 cmsg->cmsg_len == CMSG_LEN(sizeof(nl_pktinfo))) {
225 const nl_pktinfo* pktinfo = reinterpret_cast<nl_pktinfo*>(CMSG_DATA(cmsg));
226 nlGroup = pktinfo->group;
227 }
228 }
229
230 NetlinkMessage nlmsg(m_buffer.data(), static_cast<size_t>(nBytesRead));
231 for (; nlmsg.isValid(); nlmsg = nlmsg.getNext()) {
232 NDN_LOG_TRACE("parsing " << (nlmsg->nlmsg_flags & NLM_F_MULTI ? "multi-part " : "") <<
233 "message type=" << nlmsg->nlmsg_type << nlmsgTypeToString(nlmsg->nlmsg_type) <<
234 " len=" << nlmsg->nlmsg_len <<
235 " seq=" << nlmsg->nlmsg_seq <<
236 " pid=" << nlmsg->nlmsg_pid <<
237 " group=" << nlGroup);
238
239 if (nlGroup == 0 && // not a multicast notification
240 (nlmsg->nlmsg_pid != m_pid || nlmsg->nlmsg_seq != m_seqNum)) { // not for us
241 NDN_LOG_TRACE("seq/pid mismatch, ignoring");
242 continue;
243 }
244
245 if (nlmsg->nlmsg_flags & NLM_F_DUMP_INTR) {
246 NDN_LOG_ERROR("dump is inconsistent");
247 BOOST_THROW_EXCEPTION(Error("Inconsistency detected in netlink dump"));
248 // TODO: discard the rest of the message and retry the dump
249 }
250
251 m_onMessage(nlmsg);
252
253 if (nlmsg->nlmsg_type == NLMSG_DONE) {
254 break;
255 }
256 }
257}
258
259RtnlSocket::RtnlSocket(boost::asio::io_service& io)
260 : NetlinkSocket(io)
261{
262}
263
264void
265RtnlSocket::open()
266{
267 NDN_LOG_TRACE("opening rtnetlink socket");
268 NetlinkSocket::open(NETLINK_ROUTE);
269}
270
271void
272RtnlSocket::sendDumpRequest(uint16_t nlmsgType)
273{
274 struct RtnlRequest
275 {
276 nlmsghdr nlh;
277 alignas(NLMSG_ALIGNTO) ifinfomsg ifi;
278 alignas(NLMSG_ALIGNTO) rtattr rta;
279 alignas(NLMSG_ALIGNTO) uint32_t rtext; // space for IFLA_EXT_MASK
280 };
281
282 auto request = make_shared<RtnlRequest>();
283 request->nlh.nlmsg_type = nlmsgType;
284 request->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
285 request->nlh.nlmsg_seq = ++m_seqNum;
286 request->nlh.nlmsg_pid = m_pid;
287 request->ifi.ifi_family = AF_UNSPEC;
288 request->rta.rta_type = IFLA_EXT_MASK;
289 request->rta.rta_len = RTA_LENGTH(sizeof(request->rtext));
290 request->rtext = RTEXT_FILTER_SKIP_STATS;
291 request->nlh.nlmsg_len = NLMSG_SPACE(sizeof(ifinfomsg)) + request->rta.rta_len;
292
293 boost::asio::async_write(*m_sock, boost::asio::buffer(request.get(), request->nlh.nlmsg_len),
294 // capture 'request' to prevent its premature deallocation
295 [request] (const boost::system::error_code& ec, size_t) {
296 if (!ec) {
297 auto type = request->nlh.nlmsg_type;
298 NDN_LOG_TRACE("sent dump request type=" << type << nlmsgTypeToString(type)
299 << " seq=" << request->nlh.nlmsg_seq);
300 }
301 else if (ec != boost::asio::error::operation_aborted) {
302 NDN_LOG_ERROR("write failed: " << ec.message());
303 BOOST_THROW_EXCEPTION(Error("Failed to send netlink request (" + ec.message() + ")"));
304 }
305 });
306}
307
308} // namespace net
309} // namespace ndn