blob: 692c8d8dbb979f5a3833e99247dc691ff65b5a53 [file] [log] [blame]
Junxiao Shi2222a612015-06-06 08:01:38 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
Davide Pesavento60f8cc12018-05-10 22:05:21 -04002/*
Davide Pesaventob6b176c2018-07-28 22:05:16 -04003 * Copyright (c) 2011-2018, Regents of the University of California.
Junxiao Shi3cd47df2015-06-07 20:58:14 -07004 *
5 * This file is part of ndn-tools (Named Data Networking Essential Tools).
6 * See AUTHORS.md for complete list of ndn-tools authors and contributors.
7 *
8 * ndn-tools is free software: you can redistribute it and/or modify it under the terms
9 * of the GNU General Public License as published by the Free Software Foundation,
10 * either version 3 of the License, or (at your option) any later version.
11 *
12 * ndn-tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
13 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14 * PURPOSE. See the GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along with
17 * ndn-tools, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
18 */
Junxiao Shi2222a612015-06-06 08:01:38 -070019
20#include "ndndump.hpp"
21
Davide Pesavento051db002018-07-22 18:56:16 -040022#include <net/ethernet.h>
23#include <netinet/ip.h>
24#include <netinet/tcp.h>
25#include <netinet/udp.h>
Junxiao Shi2222a612015-06-06 08:01:38 -070026
Junxiao Shi022bddf2016-11-24 23:15:20 +000027#include <pcap/sll.h>
28
Junxiao Shi2222a612015-06-06 08:01:38 -070029#include <iomanip>
Davide Pesaventoc0702702017-08-24 22:04:00 -040030#include <sstream>
Junxiao Shi2222a612015-06-06 08:01:38 -070031
Vince Lehman277ecf02016-02-10 16:37:48 -060032#include <ndn-cxx/lp/nack.hpp>
33#include <ndn-cxx/lp/packet.hpp>
Davide Pesaventoecd44802018-07-23 23:48:10 -040034#include <ndn-cxx/net/ethernet.hpp>
Davide Pesaventob6b176c2018-07-28 22:05:16 -040035#include <ndn-cxx/util/string-helper.hpp>
36
37#include <boost/endian/conversion.hpp>
Junxiao Shi2222a612015-06-06 08:01:38 -070038
39namespace ndn {
Junxiao Shi3cd47df2015-06-07 20:58:14 -070040namespace dump {
Junxiao Shi2222a612015-06-06 08:01:38 -070041
Davide Pesaventob6b176c2018-07-28 22:05:16 -040042namespace endian = boost::endian;
43
44class OutputFormatter : noncopyable
45{
46public:
47 OutputFormatter(std::ostream& os, std::string d)
48 : m_os(os)
49 , m_delim(std::move(d))
50 {
51 }
52
53 OutputFormatter&
54 addDelimiter()
55 {
56 if (!m_isEmpty) {
57 m_wantDelim = true;
58 }
59 return *this;
60 }
61
62private:
63 std::ostream& m_os;
64 std::string m_delim;
65 bool m_isEmpty = true;
66 bool m_wantDelim = false;
67
68 template<typename T>
69 friend OutputFormatter& operator<<(OutputFormatter&, const T&);
70};
71
72template<typename T>
73OutputFormatter&
74operator<<(OutputFormatter& out, const T& val)
75{
76 if (out.m_wantDelim) {
77 out.m_os << out.m_delim;
78 out.m_wantDelim = false;
79 }
80 out.m_os << val;
81 out.m_isEmpty = false;
82 return out;
83}
84
Davide Pesaventoecd44802018-07-23 23:48:10 -040085NdnDump::~NdnDump()
Junxiao Shic1c2b832016-07-24 20:45:36 +000086{
Davide Pesaventoecd44802018-07-23 23:48:10 -040087 if (m_pcap)
88 pcap_close(m_pcap);
89}
90
Junxiao Shi2222a612015-06-06 08:01:38 -070091void
Davide Pesaventoecd44802018-07-23 23:48:10 -040092NdnDump::run()
Junxiao Shi2222a612015-06-06 08:01:38 -070093{
Davide Pesaventoecd44802018-07-23 23:48:10 -040094 char errbuf[PCAP_ERRBUF_SIZE];
Junxiao Shi2222a612015-06-06 08:01:38 -070095
Davide Pesaventoecd44802018-07-23 23:48:10 -040096 if (inputFile.empty() && interface.empty()) {
97 const char* defaultDevice = pcap_lookupdev(errbuf);
98
99 if (defaultDevice == nullptr) {
Junxiao Shic7599632016-07-24 20:46:24 +0000100 BOOST_THROW_EXCEPTION(Error(errbuf));
Junxiao Shi2222a612015-06-06 08:01:38 -0700101 }
102
Davide Pesaventoecd44802018-07-23 23:48:10 -0400103 interface = defaultDevice;
Junxiao Shi2222a612015-06-06 08:01:38 -0700104 }
105
Davide Pesaventoecd44802018-07-23 23:48:10 -0400106 std::string action;
Junxiao Shi2222a612015-06-06 08:01:38 -0700107 if (!interface.empty()) {
Davide Pesavento24c08612018-07-26 13:33:24 -0400108 m_pcap = pcap_open_live(interface.data(), 65535, wantPromisc, 1000, errbuf);
Junxiao Shic1c2b832016-07-24 20:45:36 +0000109 if (m_pcap == nullptr) {
Davide Pesaventoecd44802018-07-23 23:48:10 -0400110 BOOST_THROW_EXCEPTION(Error("Cannot open interface " + interface + ": " + errbuf));
Junxiao Shi2222a612015-06-06 08:01:38 -0700111 }
Davide Pesaventoecd44802018-07-23 23:48:10 -0400112 action = "listening on " + interface;
Junxiao Shi2222a612015-06-06 08:01:38 -0700113 }
114 else {
Davide Pesavento78de7352018-07-22 00:35:45 -0400115 m_pcap = pcap_open_offline(inputFile.data(), errbuf);
Junxiao Shic1c2b832016-07-24 20:45:36 +0000116 if (m_pcap == nullptr) {
Davide Pesaventoecd44802018-07-23 23:48:10 -0400117 BOOST_THROW_EXCEPTION(Error("Cannot open file '" + inputFile + "' for reading: " + errbuf));
Junxiao Shi2222a612015-06-06 08:01:38 -0700118 }
Davide Pesaventoecd44802018-07-23 23:48:10 -0400119 action = "reading from file " + inputFile;
Junxiao Shi2222a612015-06-06 08:01:38 -0700120 }
121
Davide Pesaventoecd44802018-07-23 23:48:10 -0400122 m_dataLinkType = pcap_datalink(m_pcap);
123 const char* dltName = pcap_datalink_val_to_name(m_dataLinkType);
124 const char* dltDesc = pcap_datalink_val_to_description(m_dataLinkType);
125 std::string formattedDlt = dltName ? dltName : to_string(m_dataLinkType);
126 if (dltDesc) {
127 formattedDlt += " ("s + dltDesc + ")";
128 }
129
130 std::cerr << "ndndump: " << action << ", link-type " << formattedDlt << std::endl;
131
132 switch (m_dataLinkType) {
133 case DLT_EN10MB:
134 case DLT_LINUX_SLL:
135 case DLT_PPP:
136 // we know how to handle these
137 break;
138 default:
139 BOOST_THROW_EXCEPTION(Error("Unsupported link-layer header type " + formattedDlt));
140 }
141
142 if (!pcapFilter.empty()) {
Davide Pesavento24c08612018-07-26 13:33:24 -0400143 if (wantVerbose) {
Davide Pesaventoecd44802018-07-23 23:48:10 -0400144 std::cerr << "ndndump: using pcap filter: " << pcapFilter << std::endl;
Junxiao Shi2222a612015-06-06 08:01:38 -0700145 }
146
147 bpf_program program;
Davide Pesaventofb42a3d2018-07-26 12:33:14 -0400148 int res = pcap_compile(m_pcap, &program, pcapFilter.data(), 1, PCAP_NETMASK_UNKNOWN);
Junxiao Shic1c2b832016-07-24 20:45:36 +0000149 if (res < 0) {
Davide Pesaventofb42a3d2018-07-26 12:33:14 -0400150 BOOST_THROW_EXCEPTION(Error("Cannot compile pcap filter '" + pcapFilter + "': " + pcap_geterr(m_pcap)));
Junxiao Shi2222a612015-06-06 08:01:38 -0700151 }
152
Junxiao Shic1c2b832016-07-24 20:45:36 +0000153 res = pcap_setfilter(m_pcap, &program);
Junxiao Shi2222a612015-06-06 08:01:38 -0700154 pcap_freecode(&program);
Junxiao Shic1c2b832016-07-24 20:45:36 +0000155 if (res < 0) {
Davide Pesaventoecd44802018-07-23 23:48:10 -0400156 BOOST_THROW_EXCEPTION(Error("Cannot set pcap filter: "s + pcap_geterr(m_pcap)));
Junxiao Shi2222a612015-06-06 08:01:38 -0700157 }
158 }
159
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400160 auto callback = [] (uint8_t* user, const pcap_pkthdr* pkthdr, const uint8_t* payload) {
161 reinterpret_cast<const NdnDump*>(user)->printPacket(pkthdr, payload);
162 };
163
164 if (pcap_loop(m_pcap, -1, callback, reinterpret_cast<uint8_t*>(this)) < 0) {
Davide Pesaventoecd44802018-07-23 23:48:10 -0400165 BOOST_THROW_EXCEPTION(Error("pcap_loop: "s + pcap_geterr(m_pcap)));
Junxiao Shic1c2b832016-07-24 20:45:36 +0000166 }
Junxiao Shi2222a612015-06-06 08:01:38 -0700167}
168
Junxiao Shi2222a612015-06-06 08:01:38 -0700169void
Davide Pesaventoecd44802018-07-23 23:48:10 -0400170NdnDump::printPacket(const pcap_pkthdr* pkthdr, const uint8_t* payload) const
Junxiao Shi2222a612015-06-06 08:01:38 -0700171{
Davide Pesaventoecd44802018-07-23 23:48:10 -0400172 // sanity checks
173 if (pkthdr->caplen == 0) {
174 std::cout << "[Invalid header: caplen=0]" << std::endl;
175 return;
176 }
177 if (pkthdr->len == 0) {
178 std::cout << "[Invalid header: len=0]" << std::endl;
179 return;
180 }
181 else if (pkthdr->len < pkthdr->caplen) {
182 std::cout << "[Invalid header: len(" << pkthdr->len
183 << ") < caplen(" << pkthdr->caplen << ")]" << std::endl;
184 return;
185 }
Junxiao Shi2222a612015-06-06 08:01:38 -0700186
Davide Pesaventoecd44802018-07-23 23:48:10 -0400187 std::ostringstream os;
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400188 OutputFormatter out(os, ", ");
Davide Pesaventoecd44802018-07-23 23:48:10 -0400189
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400190 bool shouldPrint = false;
191 switch (m_dataLinkType) {
192 case DLT_EN10MB:
193 shouldPrint = printEther(out, payload, pkthdr->len);
194 break;
195 case DLT_LINUX_SLL:
196 shouldPrint = printLinuxSll(out, payload, pkthdr->len);
197 break;
198 case DLT_PPP:
199 shouldPrint = printPpp(out, payload, pkthdr->len);
200 break;
201 default:
202 BOOST_ASSERT(false);
Junxiao Shi2222a612015-06-06 08:01:38 -0700203 return;
204 }
205
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400206 if (shouldPrint) {
207 if (wantTimestamp) {
208 printTimestamp(std::cout, pkthdr->ts);
Vince Lehman277ecf02016-02-10 16:37:48 -0600209 }
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400210 std::cout << os.str() << std::endl;
Junxiao Shi2222a612015-06-06 08:01:38 -0700211 }
212}
213
214void
Davide Pesaventoecd44802018-07-23 23:48:10 -0400215NdnDump::printTimestamp(std::ostream& os, const timeval& tv) const
Junxiao Shi2222a612015-06-06 08:01:38 -0700216{
Davide Pesaventoecd44802018-07-23 23:48:10 -0400217 /// \todo Add more timestamp formats (time since previous packet, time since first packet, ...)
218 os << tv.tv_sec
Junxiao Shi2222a612015-06-06 08:01:38 -0700219 << "."
Davide Pesaventoecd44802018-07-23 23:48:10 -0400220 << std::setfill('0') << std::setw(6) << tv.tv_usec
221 << " ";
Junxiao Shi2222a612015-06-06 08:01:38 -0700222}
223
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400224bool
225NdnDump::dispatchByEtherType(OutputFormatter& out, const uint8_t* pkt, size_t len, uint16_t etherType) const
Junxiao Shi2222a612015-06-06 08:01:38 -0700226{
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400227 out.addDelimiter();
Junxiao Shi2222a612015-06-06 08:01:38 -0700228
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400229 switch (etherType) {
230 case ETHERTYPE_IP:
231 return printIp4(out, pkt, len);
232 case ethernet::ETHERTYPE_NDN:
233 case 0x7777: // NDN ethertype used in ndnSIM
234 out << "Tunnel Type: EthernetFrame";
235 return printNdn(out, pkt, len);
236 default:
237 out << "Unsupported ethertype " << AsHex{etherType};
238 return true;
Junxiao Shi2222a612015-06-06 08:01:38 -0700239 }
Junxiao Shi2222a612015-06-06 08:01:38 -0700240}
241
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400242bool
243NdnDump::dispatchByIpProto(OutputFormatter& out, const uint8_t* pkt, size_t len, uint8_t ipProto) const
Junxiao Shi2222a612015-06-06 08:01:38 -0700244{
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400245 out.addDelimiter();
Junxiao Shi2222a612015-06-06 08:01:38 -0700246
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400247 switch (ipProto) {
248 case IPPROTO_TCP:
249 return printTcp(out, pkt, len);
250 case IPPROTO_UDP:
251 return printUdp(out, pkt, len);
252 default:
253 out << "Unsupported IP proto " << ipProto;
254 return true;
255 }
256}
Junxiao Shi2222a612015-06-06 08:01:38 -0700257
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400258bool
259NdnDump::printEther(OutputFormatter& out, const uint8_t* pkt, size_t len) const
260{
261 // IEEE 802.3 Ethernet
Junxiao Shi2222a612015-06-06 08:01:38 -0700262
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400263 if (len < ethernet::HDR_LEN) {
264 out << "Invalid Ethernet frame, length " << len;
265 return true;
Junxiao Shic1c2b832016-07-24 20:45:36 +0000266 }
Junxiao Shi2222a612015-06-06 08:01:38 -0700267
Davide Pesaventob6b176c2018-07-28 22:05:16 -0400268 auto ether = reinterpret_cast<const ether_header*>(pkt);
269 pkt += ethernet::HDR_LEN;
270 len -= ethernet::HDR_LEN;
271
272 return dispatchByEtherType(out, pkt, len, endian::big_to_native(ether->ether_type));
273}
274
275bool
276NdnDump::printLinuxSll(OutputFormatter& out, const uint8_t* pkt, size_t len) const
277{
278 // Linux "cooked" capture encapsulation
279 // https://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL.html
280
281 if (len < SLL_HDR_LEN) {
282 out << "Invalid LINUX_SLL frame, length " << len;
283 return true;
284 }
285
286 auto sll = reinterpret_cast<const sll_header*>(pkt);
287 pkt += SLL_HDR_LEN;
288 len -= SLL_HDR_LEN;
289
290 return dispatchByEtherType(out, pkt, len, endian::big_to_native(sll->sll_protocol));
291}
292
293bool
294NdnDump::printPpp(OutputFormatter& out, const uint8_t* pkt, size_t len) const
295{
296 // PPP, as per RFC 1661 and RFC 1662; if the first 2 bytes are 0xff and 0x03,
297 // it's PPP in HDLC-like framing, with the PPP header following those two bytes,
298 // otherwise it's PPP without framing, and the packet begins with the PPP header.
299 // The data in the frame is not octet-stuffed or bit-stuffed.
300
301 if (len < 2) {
302 out << "Invalid PPP frame, length " << len;
303 return true;
304 }
305
306 if (pkt[0] == 0xff && pkt[1] == 0x03) {
307 // PPP in HDLC-like framing, skip the Address and Control fields
308 if (len < 4) {
309 out << "Invalid PPP frame, length " << len;
310 return true;
311 }
312 pkt += 2;
313 len -= 2;
314 }
315
316 unsigned int proto = pkt[0];
317 if (proto & 0x1) {
318 // Protocol field is compressed in 1 byte
319 pkt += 1;
320 len -= 1;
321 }
322 else {
323 // Protocol field is 2 bytes, in network byte order
324 proto = (proto << 8) | pkt[1];
325 pkt += 2;
326 len -= 2;
327 }
328
329 switch (proto) {
330 case 0x0077: // NDN in PPP frame, used by ndnSIM pcap traces
331 // For some reason, ndnSIM produces PPP frames with 2 extra bytes
332 // between the protocol field and the beginning of the NDN packet
333 if (len < 2) {
334 out << "Invalid NDN/PPP frame";
335 return true;
336 }
337 pkt += 2;
338 len -= 2;
339 out << "Tunnel Type: PPP";
340 return printNdn(out, pkt, len);
341 default:
342 out << "Unsupported PPP proto " << AsHex{proto};
343 return true;
344 }
345}
346
347bool
348NdnDump::printIp4(OutputFormatter& out, const uint8_t* pkt, size_t len) const
349{
350 out.addDelimiter();
351
352 auto ih = reinterpret_cast<const ip*>(pkt);
353 size_t ipHeaderSize = ih->ip_hl * 4;
354 if (ipHeaderSize < 20) {
355 out << "Invalid IPv4 header len: " << ipHeaderSize << " bytes";
356 return true;
357 }
358
359 out << "From: " << inet_ntoa(ih->ip_src) << ", ";
360 out << "To: " << inet_ntoa(ih->ip_dst);
361
362 if (len < ipHeaderSize) {
363 out << "Invalid IPv4 packet, length " << len;
364 return true;
365 }
366
367 pkt += ipHeaderSize;
368 len -= ipHeaderSize;
369
370 return dispatchByIpProto(out, pkt, len, ih->ip_p);
371}
372
373bool
374NdnDump::printTcp(OutputFormatter& out, const uint8_t* pkt, size_t len) const
375{
376 out.addDelimiter();
377
378 auto th = reinterpret_cast<const tcphdr*>(pkt);
379 size_t tcpHeaderSize = th->th_off * 4;
380 if (tcpHeaderSize < 20) {
381 out << "Invalid TCP header len: " << tcpHeaderSize << " bytes";
382 return true;
383 }
384
385 if (len < tcpHeaderSize) {
386 out << "Invalid TCP packet, length " << len;
387 return true;
388 }
389
390 pkt += tcpHeaderSize;
391 len -= tcpHeaderSize;
392
393 out << "Tunnel Type: TCP";
394 return printNdn(out, pkt, len);
395}
396
397bool
398NdnDump::printUdp(OutputFormatter& out, const uint8_t* pkt, size_t len) const
399{
400 out.addDelimiter();
401
402 if (len < sizeof(udphdr)) {
403 out << "Invalid UDP packet, length " << len;
404 return true;
405 }
406
407 pkt += sizeof(udphdr);
408 len -= sizeof(udphdr);
409
410 out << "Tunnel Type: UDP";
411 return printNdn(out, pkt, len);
412}
413
414bool
415NdnDump::printNdn(OutputFormatter& out, const uint8_t* pkt, size_t len) const
416{
417 if (len == 0) {
418 return false;
419 }
420 out.addDelimiter();
421
422 bool isOk = false;
423 Block block;
424 std::tie(isOk, block) = Block::fromBuffer(pkt, len);
425 if (!isOk) {
426 // if packet is incomplete, we will not be able to process it
427 out << "INCOMPLETE-PACKET, length " << len;
428 return true;
429 }
430
431 lp::Packet lpPacket;
432 Block netPacket;
433
434 if (block.type() == lp::tlv::LpPacket) {
435 try {
436 lpPacket.wireDecode(block);
437 }
438 catch (const tlv::Error& e) {
439 out << "INVALID-NDNLPv2-PACKET: " << e.what();
440 return true;
441 }
442
443 Buffer::const_iterator begin, end;
444 if (lpPacket.has<lp::FragmentField>()) {
445 std::tie(begin, end) = lpPacket.get<lp::FragmentField>();
446 }
447 else {
448 out << "NDNLPv2-IDLE";
449 return true;
450 }
451
452 bool isOk = false;
453 std::tie(isOk, netPacket) = Block::fromBuffer(&*begin, std::distance(begin, end));
454 if (!isOk) {
455 // if network packet is fragmented, we will not be able to process it
456 out << "NDNLPv2-FRAGMENT";
457 return true;
458 }
459 }
460 else {
461 netPacket = std::move(block);
462 }
463
464 try {
465 switch (netPacket.type()) {
466 case tlv::Interest: {
467 Interest interest(netPacket);
468 if (!matchesFilter(interest.getName())) {
469 return false;
470 }
471
472 if (lpPacket.has<lp::NackField>()) {
473 lp::Nack nack(interest);
474 nack.setHeader(lpPacket.get<lp::NackField>());
475 out << "NACK: " << nack.getReason() << ", " << interest;
476 }
477 else {
478 out << "INTEREST: " << interest;
479 }
480 break;
481 }
482 case tlv::Data: {
483 Data data(netPacket);
484 if (!matchesFilter(data.getName())) {
485 return false;
486 }
487
488 out << "DATA: " << data.getName();
489 break;
490 }
491 default: {
492 out << "UNKNOWN-NETWORK-PACKET";
493 break;
494 }
495 }
496 }
497 catch (const tlv::Error& e) {
498 out << "INVALID-PACKET: " << e.what();
499 }
500
501 return true;
Junxiao Shi2222a612015-06-06 08:01:38 -0700502}
503
Junxiao Shic1c2b832016-07-24 20:45:36 +0000504bool
Davide Pesaventoecd44802018-07-23 23:48:10 -0400505NdnDump::matchesFilter(const Name& name) const
Junxiao Shic1c2b832016-07-24 20:45:36 +0000506{
Davide Pesavento78de7352018-07-22 00:35:45 -0400507 if (!nameFilter)
Junxiao Shic1c2b832016-07-24 20:45:36 +0000508 return true;
509
510 /// \todo Switch to NDN regular expressions
Davide Pesavento78de7352018-07-22 00:35:45 -0400511 return std::regex_match(name.toUri(), *nameFilter);
Junxiao Shic1c2b832016-07-24 20:45:36 +0000512}
513
Junxiao Shi3cd47df2015-06-07 20:58:14 -0700514} // namespace dump
Junxiao Shi2222a612015-06-06 08:01:38 -0700515} // namespace ndn