dump: Capture and print network NACK packets
refs: #3463
Change-Id: I3aed0d3668378305404f9713cc110e13eec434e5
diff --git a/.waf-tools/default-compiler-flags.py b/.waf-tools/default-compiler-flags.py
index 9f15fcc..91e9a7b 100644
--- a/.waf-tools/default-compiler-flags.py
+++ b/.waf-tools/default-compiler-flags.py
@@ -167,6 +167,9 @@
def getDebugFlags(self, conf):
flags = super(ClangFlags, self).getDebugFlags(conf)
+ version = tuple(int(i) for i in conf.env['CC_VERSION'])
+ if Utils.unversioned_sys_platform() == 'darwin' and version < (7, 0, 0):
+ flags['CXXFLAGS'] += ['-Wno-missing-field-initializers']
flags['CXXFLAGS'] += ['-fcolor-diagnostics',
'-Wno-unused-local-typedef', # Bugs #2657 and #3209
'-Wno-error=unneeded-internal-declaration', # Bug #1588
@@ -177,6 +180,9 @@
def getOptimizedFlags(self, conf):
flags = super(ClangFlags, self).getOptimizedFlags(conf)
+ version = tuple(int(i) for i in conf.env['CC_VERSION'])
+ if Utils.unversioned_sys_platform() == 'darwin' and version < (7, 0, 0):
+ flags['CXXFLAGS'] += ['-Wno-missing-field-initializers']
flags['CXXFLAGS'] += ['-fcolor-diagnostics',
'-Wno-unused-local-typedef', # Bugs #2657 and #3209
]
diff --git a/AUTHORS.md b/AUTHORS.md
index 93c29e2..7647fe3 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -19,3 +19,4 @@
* Wentao Shang <http://irl.cs.ucla.edu/~wentao/>
* Steve DiBenedetto <https://dibenede.github.io>
* Andrea Tosatto <https://linkedin.com/in/tosattoandrea>
+* Vince Lehman <http://vslehman.com>
diff --git a/tests/dump/README.md b/tests/dump/README.md
new file mode 100644
index 0000000..47fbf7f
--- /dev/null
+++ b/tests/dump/README.md
@@ -0,0 +1,29 @@
+Testing Instructions
+====================
+
+This folder contains a tcpdump trace that can be used to manually check
+the correctness of ndndump.
+
+## Test Cases / Trace File Description
+
+### 1. NDNLPv2 NACK
+
+Trace file: `nack.pcap`
+
+Trace summary: Six IPv4 UDP packets, carrying NDN interests and NACK packets.
+Twelve IPv4 TCP packets, carrying NDN interests and NACK packets.
+
+Expected result of the capture:
+
+- 3 NDN interests are captured with UDP tunnel type.
+- 3 NACKs are captured with UDP tunnel type and the NACK reasons:
+ - Congestion
+ - Duplicate
+ - None
+
+- 3 NDN interests are captured with TCP tunnel type.
+- 4 NACKs are captured with TCP tunnel type and the NACK reasons:
+ - Congestion
+ - Duplicate
+ - None
+ - None
diff --git a/tests/dump/nack.pcap b/tests/dump/nack.pcap
new file mode 100644
index 0000000..ab177f3
--- /dev/null
+++ b/tests/dump/nack.pcap
Binary files differ
diff --git a/tests/dump/ndndump.t.cpp b/tests/dump/ndndump.t.cpp
new file mode 100644
index 0000000..d4b8396
--- /dev/null
+++ b/tests/dump/ndndump.t.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016, University of Memphis.
+ *
+ * This file is part of ndn-tools (Named Data Networking Essential Tools).
+ * See AUTHORS.md for complete list of ndn-tools authors and contributors.
+ *
+ * ndn-tools is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * ndn-tools, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tools/dump/ndndump.hpp"
+
+#include "tests/test-common.hpp"
+
+#include <boost/test/output_test_stream.hpp>
+
+#include <ndn-cxx/lp/packet.hpp>
+#include <ndn-cxx/security/key-chain.hpp>
+#include <ndn-cxx/util/ethernet.hpp>
+
+namespace ndn {
+namespace dump {
+namespace tests {
+
+using namespace ndn::tests;
+
+class StdCoutRedirector
+{
+public:
+ StdCoutRedirector(std::ostream& os)
+ {
+ // Redirect std::cout to the specified output stream
+ originalBuffer = std::cout.rdbuf(os.rdbuf());
+ }
+
+ ~StdCoutRedirector()
+ {
+ // Revert state for std::cout
+ std::cout.rdbuf(originalBuffer);
+ }
+
+private:
+ std::streambuf* originalBuffer;
+};
+
+class NdnDumpFixture
+{
+protected:
+ NdnDumpFixture()
+ {
+ dump.m_dataLinkType = DLT_EN10MB;
+ }
+
+ template <typename Packet>
+ void
+ receive(const Packet& packet)
+ {
+ ndn::EncodingBuffer buffer(packet.wireEncode());
+ receive(buffer);
+ }
+
+ void
+ receive(ndn::EncodingBuffer& buffer)
+ {
+ ndn::util::ethernet::Address host;
+
+ // Ethernet header
+ uint16_t frameType = htons(ndn::util::ethernet::ETHERTYPE_NDN);
+ buffer.prependByteArray(reinterpret_cast<const uint8_t*>(&frameType),
+ ndn::util::ethernet::TYPE_LEN);
+ buffer.prependByteArray(host.data(), host.size());
+ buffer.prependByteArray(host.data(), host.size());
+
+ pcap_pkthdr header{};
+ header.len = buffer.size();
+
+ {
+ StdCoutRedirector redirect(output);
+ dump.onCapturedPacket(&header, buffer.buf());
+ }
+ }
+
+protected:
+ Ndndump dump;
+ boost::test_tools::output_test_stream output;
+};
+
+BOOST_FIXTURE_TEST_SUITE(NdnDump, NdnDumpFixture)
+
+BOOST_AUTO_TEST_CASE(CaptureInterest)
+{
+ ndn::Interest interest("/test");
+ interest.setNonce(0);
+
+ this->receive(interest);
+
+ const std::string expectedOutput =
+ "0.000000 Tunnel Type: EthernetFrame, INTEREST: /test?ndn.Nonce=0\n";
+
+ BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(CaptureData)
+{
+ ndn::security::KeyChain keyChain;
+
+ ndn::Data data("/test");
+ keyChain.sign(data);
+
+ this->receive(data);
+
+ const std::string expectedOutput = "0.000000 Tunnel Type: EthernetFrame, DATA: /test\n";
+
+ BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(CaptureNack)
+{
+ ndn::Interest interest("/test");
+ interest.setNonce(0);
+
+ ndn::lp::Nack nack(interest);
+ nack.setReason(ndn::lp::NackReason::DUPLICATE);
+
+ lp::Packet lpPacket(interest.wireEncode());
+ lpPacket.add<lp::NackField>(nack.getHeader());
+
+ this->receive(lpPacket);
+
+ const std::string expectedOutput =
+ "0.000000 Tunnel Type: EthernetFrame, NACK: Duplicate, /test?ndn.Nonce=0\n";
+
+ BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(CaptureLpFragment)
+{
+ const uint8_t data[10] = {
+ 0x06, 0x08, // Data packet
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ };
+
+ ndn::Buffer buffer(data, 4);
+
+ lp::Packet lpPacket;
+ lpPacket.add<lp::FragmentField>(std::make_pair(buffer.begin(), buffer.end()));
+ lpPacket.add<lp::FragIndexField>(0);
+ lpPacket.add<lp::FragCountField>(2);
+ lpPacket.add<lp::SequenceField>(1000);
+
+ this->receive(lpPacket);
+
+ const std::string expectedOutput =
+ "0.000000 Tunnel Type: EthernetFrame, NDNLPv2-FRAGMENT\n";
+
+ BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(CaptureIdlePacket)
+{
+ lp::Packet lpPacket;
+
+ this->receive(lpPacket);
+
+ const std::string expectedOutput =
+ "0.000000 Tunnel Type: EthernetFrame, NDNLPv2-IDLE\n";
+
+ BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(CaptureIncompletePacket)
+{
+ const uint8_t interest[] = {
+ 0x05, 0x0E, // Interest
+ 0x07, 0x06, // Name
+ 0x08, 0x04, // NameComponent
+ 0x74, 0x65, 0x73, 0x74,
+ 0x0a, 0x04, // Nonce
+ 0x00, 0x00, 0x00, 0x01
+ };
+
+ EncodingBuffer buffer;
+ buffer.prependByteArray(interest, 4);
+
+ this->receive(buffer);
+
+ const std::string expectedOutput =
+ "0.000000 Tunnel Type: EthernetFrame, INCOMPLETE-PACKET, size: 4\n";
+
+ BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(CaptureUnknownNetworkPacket)
+{
+ EncodingBuffer buffer(ndn::encoding::makeEmptyBlock(tlv::Name));
+
+ this->receive(buffer);
+
+ const std::string expectedOutput =
+ "0.000000 Tunnel Type: EthernetFrame, UNKNOWN-NETWORK-PACKET\n";
+
+ BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(DumpPcapTrace)
+{
+ dump.inputFile = "tests/dump/nack.pcap";
+ dump.pcapProgram = "";
+
+ {
+ StdCoutRedirector redirect(output);
+ dump.run();
+ }
+
+ const std::string expectedOutput =
+ "1456768916.467099 From: 1.0.0.1, To: 1.0.0.2, Tunnel Type: UDP, INTEREST: /producer/nack/congestion?ndn.MustBeFresh=1&ndn.Nonce=2581361680\n"
+ "1456768916.567099 From: 1.0.0.1, To: 1.0.0.2, Tunnel Type: UDP, INTEREST: /producer/nack/duplicate?ndn.MustBeFresh=1&ndn.Nonce=4138343109\n"
+ "1456768916.667099 From: 1.0.0.1, To: 1.0.0.2, Tunnel Type: UDP, INTEREST: /producer/nack/no-reason?ndn.MustBeFresh=1&ndn.Nonce=4034910304\n"
+ "1456768916.767099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: UDP, NACK: Congestion, /producer/nack/congestion?ndn.MustBeFresh=1&ndn.Nonce=2581361680\n"
+ "1456768916.867099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: UDP, NACK: Duplicate, /producer/nack/duplicate?ndn.MustBeFresh=1&ndn.Nonce=4138343109\n"
+ "1456768916.967099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: UDP, NACK: None, /producer/nack/no-reason?ndn.MustBeFresh=1&ndn.Nonce=4034910304\n"
+ "1456768917.067099 From: 1.0.0.1, To: 1.0.0.2, Tunnel Type: TCP, INTEREST: /producer/nack/congestion?ndn.MustBeFresh=1&ndn.Nonce=3192497423\n"
+ "1456768917.267099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: TCP, NACK: Congestion, /producer/nack/congestion?ndn.MustBeFresh=1&ndn.Nonce=3192497423\n"
+ "1456768917.367099 From: 1.0.0.1, To: 1.0.0.2, Tunnel Type: TCP, INTEREST: /producer/nack/duplicate?ndn.MustBeFresh=1&ndn.Nonce=522390724\n"
+ "1456768917.567099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: TCP, NACK: Duplicate, /producer/nack/duplicate?ndn.MustBeFresh=1&ndn.Nonce=522390724\n"
+ "1456768917.767099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: TCP, NACK: None, /producer/nack/no-reason?ndn.MustBeFresh=1&ndn.Nonce=2002441365\n"
+ "1456768917.967099 From: 1.0.0.1, To: 1.0.0.2, Tunnel Type: TCP, INTEREST: /producer/nack/no-reason?ndn.MustBeFresh=1&ndn.Nonce=3776824408\n"
+ "1456768918.067099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: TCP, NACK: None, /producer/nack/no-reason?ndn.MustBeFresh=1&ndn.Nonce=3776824408\n";
+
+ BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace dump
+} // namespace ndn
diff --git a/tools/dump/ndndump.cpp b/tools/dump/ndndump.cpp
index cf8f839..e964c58 100644
--- a/tools/dump/ndndump.cpp
+++ b/tools/dump/ndndump.cpp
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
- * Copyright (c) 2014-2015, Regents of the University of California.
+ * Copyright (c) 2014-2016, Regents of the University of California.
*
* This file is part of ndn-tools (Named Data Networking Essential Tools).
* See AUTHORS.md for complete list of ndn-tools authors and contributors.
@@ -47,8 +47,8 @@
#include "tcpdump/udp.h"
#include "tcpdump/tcp.h"
-} // namespace ndn
} // namespace dump
+} // namespace ndn
#include <boost/lexical_cast.hpp>
@@ -56,12 +56,12 @@
#include <ndn-cxx/interest.hpp>
#include <ndn-cxx/data.hpp>
+#include <ndn-cxx/lp/nack.hpp>
+#include <ndn-cxx/lp/packet.hpp>
namespace ndn {
namespace dump {
-// const uint8_t NDNLP_HEADER[] = {'N', 'd', 'n', 'l', 'p'};
-
const size_t MAX_SNAPLEN = 65535;
void
@@ -139,7 +139,7 @@
void
-Ndndump::onCapturedPacket(const struct pcap_pkthdr* header, const uint8_t* packet)
+Ndndump::onCapturedPacket(const pcap_pkthdr* header, const uint8_t* packet)
{
std::ostringstream os;
printInterceptTime(os, header);
@@ -162,25 +162,66 @@
Block block;
std::tie(isOk, block) = Block::fromBuffer(payload, payloadSize);
if (!isOk) {
- // if packet is fragmented, we will not be able to process it
+ // if packet is incomplete, we will not be able to process it
+ if (payloadSize > 0) {
+ std::cout << os.str() << ", " << "INCOMPLETE-PACKET" << ", size: " << payloadSize << std::endl;
+ }
return;
}
- /// \todo Detect various header (LocalControlHeader, NDNLP, etc.)
+ lp::Packet lpPacket;
+ Block netPacket;
+
+ if (block.type() == lp::tlv::LpPacket) {
+ lpPacket = lp::Packet(block);
+
+ Buffer::const_iterator begin, end;
+
+ if (lpPacket.has<lp::FragmentField>()) {
+ std::tie(begin, end) = lpPacket.get<lp::FragmentField>();
+ }
+ else {
+ std::cout << os.str() << ", " << "NDNLPv2-IDLE" << std::endl;
+ return;
+ }
+
+ bool isOk = false;
+ std::tie(isOk, netPacket) = Block::fromBuffer(&*begin, std::distance(begin, end));
+ if (!isOk) {
+ // if network packet is fragmented, we will not be able to process it
+ std::cout << os.str() << ", " << "NDNLPv2-FRAGMENT" << std::endl;
+ return;
+ }
+ }
+ else {
+ netPacket = block;
+ }
try {
- if (block.type() == tlv::Interest) {
- Interest interest(block);
+ if (netPacket.type() == tlv::Interest) {
+ Interest interest(netPacket);
if (matchesFilter(interest.getName())) {
- std::cout << os.str() << ", " << "INTEREST: " << interest << std::endl;
+
+ if (lpPacket.has<lp::NackField>()) {
+ lp::Nack nack(interest);
+ nack.setHeader(lpPacket.get<lp::NackField>());
+
+ std::cout << os.str() << ", " << "NACK: " << nack.getReason() << ", " << interest << std::endl;
+ }
+ else {
+ std::cout << os.str() << ", " << "INTEREST: " << interest << std::endl;
+ }
}
}
- else if (block.type() == tlv::Data) {
- Data data(block);
+ else if (netPacket.type() == tlv::Data) {
+ Data data(netPacket);
if (matchesFilter(data.getName())) {
std::cout << os.str() << ", " << "DATA: " << data.getName() << std::endl;
}
}
+ else {
+ std::cout << os.str() << ", " << "UNKNOWN-NETWORK-PACKET" << std::endl;
+ }
}
catch (tlv::Error& e) {
std::cerr << e.what() << std::endl;
@@ -188,7 +229,7 @@
}
void
-Ndndump::printInterceptTime(std::ostream& os, const struct pcap_pkthdr* header)
+Ndndump::printInterceptTime(std::ostream& os, const pcap_pkthdr* header)
{
os << header->ts.tv_sec
<< "."
diff --git a/tools/dump/ndndump.hpp b/tools/dump/ndndump.hpp
index b46c109..594406d 100644
--- a/tools/dump/ndndump.hpp
+++ b/tools/dump/ndndump.hpp
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
- * Copyright (c) 2014-2015, Regents of the University of California.
+ * Copyright (c) 2014-2016, Regents of the University of California.
*
* This file is part of ndn-tools (Named Data Networking Essential Tools).
* See AUTHORS.md for complete list of ndn-tools authors and contributors.
@@ -37,6 +37,8 @@
#ifndef NDN_TOOLS_DUMP_NDNDUMP_HPP
#define NDN_TOOLS_DUMP_NDNDUMP_HPP
+#include "core/common.hpp"
+
#include <pcap.h>
#include <ndn-cxx/name.hpp>
@@ -72,18 +74,19 @@
void
run();
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+ void
+ onCapturedPacket(const pcap_pkthdr* header, const uint8_t* packet);
+
private:
static void
- onCapturedPacket(uint8_t* userData, const struct pcap_pkthdr* header, const uint8_t* packet)
+ onCapturedPacket(uint8_t* userData, const pcap_pkthdr* header, const uint8_t* packet)
{
reinterpret_cast<Ndndump*>(userData)->onCapturedPacket(header, packet);
}
void
- onCapturedPacket(const struct pcap_pkthdr* header, const uint8_t* packet);
-
- void
- printInterceptTime(std::ostream& os, const struct pcap_pkthdr* header);
+ printInterceptTime(std::ostream& os, const pcap_pkthdr* header);
int
skipDataLinkHeaderAndGetFrameType(const uint8_t*& payload, ssize_t& payloadSize);
@@ -120,6 +123,8 @@
private:
pcap_t* m_pcap;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
int m_dataLinkType;
};