NS-3 based Named Data Networking (NDN) simulator
ndnSIM 2.0: NDN, CCN, CCNx, content centric networks
API Documentation
ethernet-face.cpp
Go to the documentation of this file.
1 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
26 #include "ethernet-face.hpp"
27 #include "core/global-io.hpp"
28 
29 #include <pcap/pcap.h>
30 
31 #include <cerrno> // for errno
32 #include <cstring> // for std::strerror() and std::strncpy()
33 #include <stdio.h> // for snprintf()
34 #include <arpa/inet.h> // for htons() and ntohs()
35 #include <net/ethernet.h> // for struct ether_header
36 #include <net/if.h> // for struct ifreq
37 #include <sys/ioctl.h> // for ioctl()
38 #include <unistd.h> // for dup()
39 
40 #if defined(__linux__)
41 #include <netpacket/packet.h> // for struct packet_mreq
42 #include <sys/socket.h> // for setsockopt()
43 #endif
44 
45 #ifdef SIOCADDMULTI
46 #if defined(__APPLE__) || defined(__FreeBSD__)
47 #include <net/if_dl.h> // for struct sockaddr_dl
48 #endif
49 #endif
50 
51 #if !defined(PCAP_NETMASK_UNKNOWN)
52 /*
53  * Value to pass to pcap_compile() as the netmask if you don't know what
54  * the netmask is.
55  */
56 #define PCAP_NETMASK_UNKNOWN 0xffffffff
57 #endif
58 
59 namespace nfd {
60 
61 NFD_LOG_INIT("EthernetFace");
62 
63 const time::nanoseconds EthernetFace::REASSEMBLER_LIFETIME = time::seconds(60);
64 
65 EthernetFace::EthernetFace(boost::asio::posix::stream_descriptor socket,
66  const NetworkInterfaceInfo& interface,
67  const ethernet::Address& address)
68  : Face(FaceUri(address), FaceUri::fromDev(interface.name), false, true)
69  , m_pcap(nullptr, pcap_close)
70  , m_socket(std::move(socket))
71 #if defined(__linux__)
72  , m_interfaceIndex(interface.index)
73 #endif
74  , m_interfaceName(interface.name)
75  , m_srcAddress(interface.etherAddress)
76  , m_destAddress(address)
77 #ifdef _DEBUG
78  , m_nDropped(0)
79 #endif
80 {
81  NFD_LOG_FACE_INFO("Creating face on " << m_interfaceName << "/" << m_srcAddress);
82  pcapInit();
83 
84  int fd = pcap_get_selectable_fd(m_pcap.get());
85  if (fd < 0)
86  BOOST_THROW_EXCEPTION(Error("pcap_get_selectable_fd failed"));
87 
88  // need to duplicate the fd, otherwise both pcap_close()
89  // and stream_descriptor::close() will try to close the
90  // same fd and one of them will fail
91  m_socket.assign(::dup(fd));
92 
93  m_interfaceMtu = getInterfaceMtu();
94  NFD_LOG_FACE_DEBUG("Interface MTU is: " << m_interfaceMtu);
95 
96  m_slicer.reset(new ndnlp::Slicer(m_interfaceMtu));
97 
98  char filter[100];
99  // std::snprintf not found in some environments
100  // http://redmine.named-data.net/issues/2299 for more information
101  snprintf(filter, sizeof(filter),
102  "(ether proto 0x%x) && (ether dst %s) && (not ether src %s)",
104  m_destAddress.toString().c_str(),
105  m_srcAddress.toString().c_str());
106  setPacketFilter(filter);
107 
108  if (!m_destAddress.isBroadcast() && !joinMulticastGroup())
109  {
110  NFD_LOG_FACE_WARN("Falling back to promiscuous mode");
111  pcap_set_promisc(m_pcap.get(), 1);
112  }
113 
114  m_socket.async_read_some(boost::asio::null_buffers(),
115  bind(&EthernetFace::handleRead, this,
116  boost::asio::placeholders::error,
117  boost::asio::placeholders::bytes_transferred));
118 }
119 
120 void
122 {
123  NFD_LOG_FACE_TRACE(__func__);
124 
125  this->emitSignal(onSendInterest, interest);
126 
127  ndnlp::PacketArray pa = m_slicer->slice(interest.wireEncode());
128  for (const auto& packet : *pa) {
129  sendPacket(packet);
130  }
131 }
132 
133 void
135 {
136  NFD_LOG_FACE_TRACE(__func__);
137 
138  this->emitSignal(onSendData, data);
139 
140  ndnlp::PacketArray pa = m_slicer->slice(data.wireEncode());
141  for (const auto& packet : *pa) {
142  sendPacket(packet);
143  }
144 }
145 
146 void
148 {
149  if (!m_pcap)
150  return;
151 
152  NFD_LOG_FACE_INFO("Closing face");
153 
154  boost::system::error_code error;
155  m_socket.cancel(error); // ignore errors
156  m_socket.close(error); // ignore errors
157  m_pcap.reset();
158 
159  fail("Face closed");
160 }
161 
162 void
163 EthernetFace::pcapInit()
164 {
165  char errbuf[PCAP_ERRBUF_SIZE] = {};
166  m_pcap.reset(pcap_create(m_interfaceName.c_str(), errbuf));
167  if (!m_pcap)
168  BOOST_THROW_EXCEPTION(Error("pcap_create: " + std::string(errbuf)));
169 
170 #ifdef HAVE_PCAP_SET_IMMEDIATE_MODE
171  // Enable "immediate mode", effectively disabling any read buffering in the kernel.
172  // This corresponds to the BIOCIMMEDIATE ioctl on BSD-like systems (including OS X)
173  // where libpcap uses a BPF device. On Linux this forces libpcap not to use TPACKET_V3,
174  // even if the kernel supports it, thus preventing bug #1511.
175  pcap_set_immediate_mode(m_pcap.get(), 1);
176 #endif
177 
178  if (pcap_activate(m_pcap.get()) < 0)
179  BOOST_THROW_EXCEPTION(Error("pcap_activate failed"));
180 
181  if (pcap_set_datalink(m_pcap.get(), DLT_EN10MB) < 0)
182  BOOST_THROW_EXCEPTION(Error("pcap_set_datalink: " + std::string(pcap_geterr(m_pcap.get()))));
183 
184  if (pcap_setdirection(m_pcap.get(), PCAP_D_IN) < 0)
185  // no need to throw on failure, BPF will filter unwanted packets anyway
186  NFD_LOG_FACE_WARN("pcap_setdirection failed: " << pcap_geterr(m_pcap.get()));
187 }
188 
189 void
190 EthernetFace::setPacketFilter(const char* filterString)
191 {
192  bpf_program filter;
193  if (pcap_compile(m_pcap.get(), &filter, filterString, 1, PCAP_NETMASK_UNKNOWN) < 0)
194  BOOST_THROW_EXCEPTION(Error("pcap_compile: " + std::string(pcap_geterr(m_pcap.get()))));
195 
196  int ret = pcap_setfilter(m_pcap.get(), &filter);
197  pcap_freecode(&filter);
198  if (ret < 0)
199  BOOST_THROW_EXCEPTION(Error("pcap_setfilter: " + std::string(pcap_geterr(m_pcap.get()))));
200 }
201 
202 bool
203 EthernetFace::joinMulticastGroup()
204 {
205 #if defined(__linux__)
206  packet_mreq mr{};
207  mr.mr_ifindex = m_interfaceIndex;
208  mr.mr_type = PACKET_MR_MULTICAST;
209  mr.mr_alen = m_destAddress.size();
210  std::copy(m_destAddress.begin(), m_destAddress.end(), mr.mr_address);
211 
212  if (::setsockopt(m_socket.native_handle(), SOL_PACKET,
213  PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr)) == 0)
214  return true; // success
215 
216  NFD_LOG_FACE_WARN("setsockopt(PACKET_ADD_MEMBERSHIP) failed: " << std::strerror(errno));
217 #endif
218 
219 #if defined(SIOCADDMULTI)
220  ifreq ifr{};
221  std::strncpy(ifr.ifr_name, m_interfaceName.c_str(), sizeof(ifr.ifr_name) - 1);
222 
223 #if defined(__APPLE__) || defined(__FreeBSD__)
224  // see bug #2327
225  using boost::asio::ip::udp;
226  udp::socket sock(ref(getGlobalIoService()), udp::v4());
227  int fd = sock.native_handle();
228 
229  /*
230  * Differences between Linux and the BSDs (including OS X):
231  * o BSD does not have ifr_hwaddr; use ifr_addr instead.
232  * o While OS X seems to accept both AF_LINK and AF_UNSPEC as the address
233  * family, FreeBSD explicitly requires AF_LINK, so we have to use AF_LINK
234  * and sockaddr_dl instead of the generic sockaddr structure.
235  * o BSD's sockaddr (and sockaddr_dl in particular) contains an additional
236  * field, sa_len (sdl_len), which must be set to the total length of the
237  * structure, including the length field itself.
238  * o We do not specify the interface name, thus sdl_nlen is left at 0 and
239  * LLADDR is effectively the same as sdl_data.
240  */
241  sockaddr_dl* sdl = reinterpret_cast<sockaddr_dl*>(&ifr.ifr_addr);
242  sdl->sdl_len = sizeof(ifr.ifr_addr);
243  sdl->sdl_family = AF_LINK;
244  sdl->sdl_alen = m_destAddress.size();
245  std::copy(m_destAddress.begin(), m_destAddress.end(), LLADDR(sdl));
246 
247  static_assert(sizeof(ifr.ifr_addr) >= offsetof(sockaddr_dl, sdl_data) + ethernet::ADDR_LEN,
248  "ifr_addr in struct ifreq is too small on this platform");
249 #else
250  int fd = m_socket.native_handle();
251 
252  ifr.ifr_hwaddr.sa_family = AF_UNSPEC;
253  std::copy(m_destAddress.begin(), m_destAddress.end(), ifr.ifr_hwaddr.sa_data);
254 
255  static_assert(sizeof(ifr.ifr_hwaddr) >= offsetof(sockaddr, sa_data) + ethernet::ADDR_LEN,
256  "ifr_hwaddr in struct ifreq is too small on this platform");
257 #endif
258 
259  if (::ioctl(fd, SIOCADDMULTI, &ifr) == 0)
260  return true; // success
261 
262  NFD_LOG_FACE_WARN("ioctl(SIOCADDMULTI) failed: " << std::strerror(errno));
263 #endif
264 
265  return false;
266 }
267 
268 void
269 EthernetFace::sendPacket(const ndn::Block& block)
270 {
271  if (!m_pcap)
272  {
273  NFD_LOG_FACE_WARN("Trying to send on closed face");
274  return fail("Face closed");
275  }
276 
277  BOOST_ASSERT(block.size() <= m_interfaceMtu);
278 
281  ndn::EncodingBuffer buffer(block);
282 
283  // pad with zeroes if the payload is too short
284  if (block.size() < ethernet::MIN_DATA_LEN)
285  {
286  static const uint8_t padding[ethernet::MIN_DATA_LEN] = {};
287  buffer.appendByteArray(padding, ethernet::MIN_DATA_LEN - block.size());
288  }
289 
290  // construct and prepend the ethernet header
291  static uint16_t ethertype = htons(ethernet::ETHERTYPE_NDN);
292  buffer.prependByteArray(reinterpret_cast<const uint8_t*>(&ethertype), ethernet::TYPE_LEN);
293  buffer.prependByteArray(m_srcAddress.data(), m_srcAddress.size());
294  buffer.prependByteArray(m_destAddress.data(), m_destAddress.size());
295 
296  // send the packet
297  int sent = pcap_inject(m_pcap.get(), buffer.buf(), buffer.size());
298  if (sent < 0)
299  {
300  return fail("pcap_inject: " + std::string(pcap_geterr(m_pcap.get())));
301  }
302  else if (static_cast<size_t>(sent) < buffer.size())
303  {
304  return fail("Failed to inject frame");
305  }
306 
307  NFD_LOG_FACE_TRACE("Successfully sent: " << block.size() << " bytes");
308  this->getMutableCounters().getNOutBytes() += block.size();
309 }
310 
311 void
312 EthernetFace::handleRead(const boost::system::error_code& error, size_t)
313 {
314  if (!m_pcap)
315  return fail("Face closed");
316 
317  if (error)
318  return processErrorCode(error);
319 
320  pcap_pkthdr* header;
321  const uint8_t* packet;
322  int ret = pcap_next_ex(m_pcap.get(), &header, &packet);
323  if (ret < 0)
324  {
325  return fail("pcap_next_ex: " + std::string(pcap_geterr(m_pcap.get())));
326  }
327  else if (ret == 0)
328  {
329  NFD_LOG_FACE_WARN("Read timeout");
330  }
331  else
332  {
333  processIncomingPacket(header, packet);
334  }
335 
336 #ifdef _DEBUG
337  pcap_stat ps{};
338  ret = pcap_stats(m_pcap.get(), &ps);
339  if (ret < 0)
340  {
341  NFD_LOG_FACE_DEBUG("pcap_stats failed: " << pcap_geterr(m_pcap.get()));
342  }
343  else if (ret == 0)
344  {
345  if (ps.ps_drop - m_nDropped > 0)
346  NFD_LOG_FACE_DEBUG("Detected " << ps.ps_drop - m_nDropped << " dropped packet(s)");
347  m_nDropped = ps.ps_drop;
348  }
349 #endif
350 
351  m_socket.async_read_some(boost::asio::null_buffers(),
352  bind(&EthernetFace::handleRead, this,
353  boost::asio::placeholders::error,
354  boost::asio::placeholders::bytes_transferred));
355 }
356 
357 void
358 EthernetFace::processIncomingPacket(const pcap_pkthdr* header, const uint8_t* packet)
359 {
360  size_t length = header->caplen;
361  if (length < ethernet::HDR_LEN + ethernet::MIN_DATA_LEN) {
362  NFD_LOG_FACE_WARN("Received frame is too short (" << length << " bytes)");
363  return;
364  }
365 
366  const ether_header* eh = reinterpret_cast<const ether_header*>(packet);
367  const ethernet::Address sourceAddress(eh->ether_shost);
368 
369  // assert in case BPF fails to filter unwanted frames
370  BOOST_ASSERT_MSG(ethernet::Address(eh->ether_dhost) == m_destAddress,
371  "Received frame addressed to a different multicast group");
372  BOOST_ASSERT_MSG(sourceAddress != m_srcAddress,
373  "Received frame sent by this host");
374  BOOST_ASSERT_MSG(ntohs(eh->ether_type) == ethernet::ETHERTYPE_NDN,
375  "Received frame with unrecognized ethertype");
376 
377  packet += ethernet::HDR_LEN;
378  length -= ethernet::HDR_LEN;
379 
381  bool isOk = false;
382  Block fragmentBlock;
383  std::tie(isOk, fragmentBlock) = Block::fromBuffer(packet, length);
384  if (!isOk) {
385  NFD_LOG_FACE_WARN("Block received from " << sourceAddress.toString()
386  << " is invalid or too large to process");
387  return;
388  }
389 
390  NFD_LOG_FACE_TRACE("Received: " << fragmentBlock.size() << " bytes from "
391  << sourceAddress.toString());
392  this->getMutableCounters().getNInBytes() += fragmentBlock.size();
393 
394  Reassembler& reassembler = m_reassemblers[sourceAddress];
395  if (!reassembler.pms) {
396  // new sender, setup a PartialMessageStore for it
397  reassembler.pms.reset(new ndnlp::PartialMessageStore);
398  reassembler.pms->onReceive.connect(
399  [this, sourceAddress] (const Block& block) {
400  NFD_LOG_FACE_TRACE("All fragments received from " << sourceAddress.toString());
401  if (!decodeAndDispatchInput(block))
402  NFD_LOG_FACE_WARN("Received unrecognized TLV block of type " << block.type()
403  << " from " << sourceAddress.toString());
404  });
405  }
406 
407  scheduler::cancel(reassembler.expireEvent);
408  reassembler.expireEvent = scheduler::schedule(REASSEMBLER_LIFETIME,
409  [this, sourceAddress] {
410  BOOST_VERIFY(m_reassemblers.erase(sourceAddress) == 1);
411  });
412 
413  ndnlp::NdnlpData fragment;
414  std::tie(isOk, fragment) = ndnlp::NdnlpData::fromBlock(fragmentBlock);
415  if (!isOk) {
416  NFD_LOG_FACE_WARN("Received invalid NDNLP fragment from " << sourceAddress.toString());
417  return;
418  }
419 
420  reassembler.pms->receive(fragment);
421 }
422 
423 void
424 EthernetFace::processErrorCode(const boost::system::error_code& error)
425 {
426  if (error == boost::asio::error::operation_aborted)
427  // cancel() has been called on the socket
428  return;
429 
430  std::string msg;
431  if (error == boost::asio::error::eof)
432  {
433  msg = "Face closed";
434  }
435  else
436  {
437  msg = "Receive operation failed: " + error.message();
438  NFD_LOG_FACE_WARN(msg);
439  }
440  fail(msg);
441 }
442 
443 size_t
444 EthernetFace::getInterfaceMtu()
445 {
446 #ifdef SIOCGIFMTU
447 #if defined(__APPLE__) || defined(__FreeBSD__)
448  // see bug #2328
449  using boost::asio::ip::udp;
450  udp::socket sock(ref(getGlobalIoService()), udp::v4());
451  int fd = sock.native_handle();
452 #else
453  int fd = m_socket.native_handle();
454 #endif
455 
456  ifreq ifr{};
457  std::strncpy(ifr.ifr_name, m_interfaceName.c_str(), sizeof(ifr.ifr_name) - 1);
458 
459  if (::ioctl(fd, SIOCGIFMTU, &ifr) == 0)
460  return static_cast<size_t>(ifr.ifr_mtu);
461 
462  NFD_LOG_FACE_WARN("Failed to get interface MTU: " << std::strerror(errno));
463 #endif
464 
465  return ethernet::MAX_DATA_LEN;
466 }
467 
468 } // namespace nfd
const size_t ADDR_LEN
Octets in one Ethernet address.
Definition: ethernet.hpp:42
static std::tuple< bool, Block > fromBuffer(ConstBufferPtr buffer, size_t offset)
Try to construct block from Buffer.
Definition: block.cpp:253
#define NFD_LOG_FACE_TRACE(msg)
Log a message at TRACE level.
Definition: face.hpp:321
provides fragmentation feature at sender
void cancel(const EventId &eventId)
cancel a scheduled event
Definition: scheduler.cpp:58
represents an Ethernet hardware address
Definition: ethernet.hpp:53
signal::Signal< Face, Interest > onSendInterest
fires when an Interest is sent out
Definition: face.hpp:86
represents the underlying protocol and address used by a Face
Definition: face-uri.hpp:44
represents a NdnlpData packet
Definition: ndnlp-data.hpp:43
contains information about a network interface
STL namespace.
bool decodeAndDispatchInput(const Block &element)
Definition: face.cpp:59
EthernetFace(boost::asio::posix::stream_descriptor socket, const NetworkInterfaceInfo &interface, const ethernet::Address &address)
void sendData(const Data &data) 1
send a Data
const size_t TYPE_LEN
Octets in Ethertype field.
Definition: ethernet.hpp:43
Class representing a wire element of NDN-TLV packet format.
Definition: block.hpp:43
represents an Interest packet
Definition: interest.hpp:45
static std::tuple< bool, NdnlpData > fromBlock(const Block &wire)
parse a NdnlpData packet
Definition: ndnlp-data.cpp:32
std::string toString(char sep= ':') const
Converts the address to a human-readable string.
Definition: ethernet.cpp:72
signal::Signal< Face, Data > onSendData
fires when a Data is sent out
Definition: face.hpp:89
void sendInterest(const Interest &interest) 1
send an Interest
represents a face
Definition: face.hpp:57
EthernetFace-related error.
bool isBroadcast() const
True if this is a broadcast address (ff:ff:ff:ff:ff:ff)
Definition: ethernet.cpp:54
const size_t MIN_DATA_LEN
Min octets in Ethernet payload (assuming no 802.1Q tag)
Definition: ethernet.hpp:46
size_t size() const
Definition: block.cpp:504
#define emitSignal(...)
(implementation detail)
Definition: signal-emit.hpp:76
#define NFD_LOG_FACE_DEBUG(msg)
Log a message at DEBUG level.
Definition: face.hpp:324
size_t wireEncode(EncodingImpl< TAG > &encoder, bool wantUnsignedPortionOnly=false) const
Fast encoding or block size estimation.
Definition: data.cpp:52
void fail(const std::string &reason)
fail the face and raise onFail event if it&#39;s UP; otherwise do nothing
Definition: face.cpp:87
EncodingImpl< EncoderTag > EncodingBuffer
#define NFD_LOG_FACE_INFO(msg)
Log a message at INFO level.
Definition: face.hpp:327
Copyright (c) 2011-2015 Regents of the University of California.
Definition: ndn-common.hpp:38
shared_ptr< std::vector< Block > > PacketArray
void close() 1
Closes the face.
size_t wireEncode(EncodingImpl< TAG > &encoder) const
Fast encoding or block size estimation.
Definition: interest.cpp:217
const ByteCounter & getNInBytes() const
received bytes
FaceCounters & getMutableCounters()
Definition: face.hpp:275
EventId schedule(const time::nanoseconds &after, const std::function< void()> &event)
schedule an event
Definition: scheduler.cpp:50
#define PCAP_NETMASK_UNKNOWN
Copyright (c) 2014-2015, Regents of the University of California, Arizona Board of Regents...
const size_t MAX_DATA_LEN
Max octets in Ethernet payload.
Definition: ethernet.hpp:47
uint32_t type() const
Definition: block.hpp:346
#define NFD_LOG_INIT(name)
Definition: logger.hpp:33
const size_t HDR_LEN
Total octets in Ethernet header (without 802.1Q tag)
Definition: ethernet.hpp:44
provides reassembly feature at receiver
boost::asio::io_service & getGlobalIoService()
Definition: global-io.hpp:35
const uint16_t ETHERTYPE_NDN
Definition: ethernet.hpp:40
represents a Data packet
Definition: data.hpp:39
const ByteCounter & getNOutBytes() const
sent bytes
#define NFD_LOG_FACE_WARN(msg)
Log a message at WARN level.
Definition: face.hpp:330