blob: 91d68279f751864d4947088140eb26b6b858222b [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
* 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.
*
* 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/>.
*/
/**
* Copyright (c) 2011-2014, Regents of the University of California,
*
* This file is part of ndndump, the packet capture and analysis tool for Named Data
* Networking (NDN).
*
* ndndump 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.
*
* ndndump 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
* ndndump, e.g., in COPYING file. If not, see <http://www.gnu.org/licenses/>.
**/
#include "ndndump.hpp"
#include "tcpdump/tcpdump-stdinc.h"
namespace ndn {
namespace dump {
// namespace is necessary to prevent clashing with system includes
#include "tcpdump/ether.h"
#include "tcpdump/ip.h"
#include "tcpdump/udp.h"
#include "tcpdump/tcp.h"
} // namespace dump
} // namespace ndn
#include <pcap/sll.h>
#include <boost/lexical_cast.hpp>
#include <iomanip>
#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 size_t MAX_SNAPLEN = 65535;
Ndndump::Ndndump()
: isVerbose(false)
, pcapProgram("(ether proto 0x8624) || (tcp port 6363) || (udp port 6363)")
// , isSuccinct(false)
// , isMatchInverted(false)
// , shouldPrintStructure(false)
// , isTcpOnly(false)
// , isUdpOnly(false)
{
}
void
Ndndump::run()
{
if (inputFile.empty() && interface.empty()) {
char errbuf[PCAP_ERRBUF_SIZE];
const char* pcapDevice = pcap_lookupdev(errbuf);
if (pcapDevice == nullptr) {
BOOST_THROW_EXCEPTION(Error(errbuf));
}
interface = pcapDevice;
}
if (isVerbose) {
if (!interface.empty()) {
std::cerr << "ndndump: listening on " << interface << std::endl;
}
else {
std::cerr << "ndndump: reading from " << inputFile << std::endl;
}
if (!nameFilter.empty()) {
std::cerr << "ndndump: using name filter " << nameFilter << std::endl;
}
}
if (!interface.empty()) {
char errbuf[PCAP_ERRBUF_SIZE];
m_pcap = pcap_open_live(interface.c_str(), MAX_SNAPLEN, 0, 1000, errbuf);
if (m_pcap == nullptr) {
BOOST_THROW_EXCEPTION(Error("Cannot open interface " + interface + " (" + errbuf + ")"));
}
}
else {
char errbuf[PCAP_ERRBUF_SIZE];
m_pcap = pcap_open_offline(inputFile.c_str(), errbuf);
if (m_pcap == nullptr) {
BOOST_THROW_EXCEPTION(Error("Cannot open file " + inputFile + " for reading (" + errbuf + ")"));
}
}
if (!pcapProgram.empty()) {
if (isVerbose) {
std::cerr << "ndndump: pcap_filter = " << pcapProgram << std::endl;
}
bpf_program program;
int res = pcap_compile(m_pcap, &program, pcapProgram.c_str(), 0, PCAP_NETMASK_UNKNOWN);
if (res < 0) {
BOOST_THROW_EXCEPTION(Error("Cannot parse tcpdump expression '" + pcapProgram +
"' (" + pcap_geterr(m_pcap) + ")"));
}
res = pcap_setfilter(m_pcap, &program);
pcap_freecode(&program);
if (res < 0) {
BOOST_THROW_EXCEPTION(Error(std::string("pcap_setfilter failed (") + pcap_geterr(m_pcap) + ")"));
}
}
m_dataLinkType = pcap_datalink(m_pcap);
if (m_dataLinkType != DLT_EN10MB && m_dataLinkType != DLT_PPP && m_dataLinkType != DLT_LINUX_SLL) {
BOOST_THROW_EXCEPTION(Error("Unsupported pcap format (" + to_string(m_dataLinkType) + ")"));
}
pcap_loop(m_pcap, -1, &Ndndump::onCapturedPacket, reinterpret_cast<uint8_t*>(this));
}
void
Ndndump::onCapturedPacket(const pcap_pkthdr* header, const uint8_t* packet) const
{
std::ostringstream os;
printInterceptTime(os, header);
const uint8_t* payload = packet;
ssize_t payloadSize = header->len;
int frameType = skipDataLinkHeaderAndGetFrameType(payload, payloadSize);
if (frameType < 0) {
std::cerr << "Unknown frame type" << std::endl;
return;
}
int res = skipAndProcessFrameHeader(frameType, payload, payloadSize, os);
if (res < 0) {
return;
}
bool isOk = false;
Block block;
std::tie(isOk, block) = Block::fromBuffer(payload, payloadSize);
if (!isOk) {
// 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;
}
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 (netPacket.type() == tlv::Interest) {
Interest interest(netPacket);
if (matchesFilter(interest.getName())) {
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 (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 (const tlv::Error& e) {
std::cerr << e.what() << std::endl;
}
}
void
Ndndump::printInterceptTime(std::ostream& os, const pcap_pkthdr* header) const
{
os << header->ts.tv_sec
<< "."
<< std::setfill('0') << std::setw(6) << header->ts.tv_usec;
// struct tm* tm;
// if (flags.unit_time) {
// os << (int) header->ts.tv_sec
// << "."
// << setfill('0') << setw(6) << (int)header->ts.tv_usec;
// } else {
// tm = localtime(&(header->ts.tv_sec));
// os << (int)tm->tm_hour << ":"
// << setfill('0') << setw(2) << (int)tm->tm_min<< ":"
// << setfill('0') << setw(2) << (int)tm->tm_sec<< "."
// << setfill('0') << setw(6) << (int)header->ts.tv_usec;
// }
os << " ";
}
int
Ndndump::skipDataLinkHeaderAndGetFrameType(const uint8_t*& payload, ssize_t& payloadSize) const
{
int frameType = 0;
switch (m_dataLinkType) {
case DLT_EN10MB: { // Ethernet frames can have Ethernet or 802.3 encapsulation
const ether_header* etherHeader = reinterpret_cast<const ether_header*>(payload);
if (payloadSize < 0) {
std::cerr << "Invalid pcap Ethernet frame" << std::endl;
return -1;
}
frameType = ntohs(etherHeader->ether_type);
payloadSize -= ETHER_HDRLEN;
payload += ETHER_HDRLEN;
break;
}
case DLT_PPP: {
frameType = *payload;
--payloadSize;
++payload;
if (!(frameType & 1)) {
frameType = (frameType << 8) | *payload;
--payloadSize;
++payload;
}
if (payloadSize < 0) {
std::cerr << "Invalid PPP frame" << std::endl;
return -1;
}
break;
}
case DLT_LINUX_SLL: {
const sll_header* sllHeader = reinterpret_cast<const sll_header*>(payload);
if (payloadSize < SLL_HDR_LEN) {
std::cerr << "Invalid LINUX_SLL frame" << std::endl;
return -1;
}
frameType = ntohs(sllHeader->sll_protocol);
payloadSize -= SLL_HDR_LEN;
payload += SLL_HDR_LEN;
break;
}
}
return frameType;
}
int
Ndndump::skipAndProcessFrameHeader(int frameType,
const uint8_t*& payload, ssize_t& payloadSize,
std::ostream& os) const
{
switch (frameType) {
case 0x0800: // ETHERTYPE_IP
case DLT_EN10MB: { // pcap encapsulation
const ip* ipHeader = reinterpret_cast<const ip*>(payload);
size_t ipHeaderSize = IP_HL(ipHeader) * 4;
if (ipHeaderSize < 20) {
std::cerr << "invalid IP header len " << ipHeaderSize << " bytes" << std::endl;
return -1;
}
os << "From: " << inet_ntoa(ipHeader->ip_src) << ", ";
os << "To: " << inet_ntoa(ipHeader->ip_dst);
payloadSize -= ipHeaderSize;
payload += ipHeaderSize;
if (payloadSize < 0) {
std::cerr << "Invalid pcap IP packet" << std::endl;
return -1;
}
switch (ipHeader->ip_p) {
case IPPROTO_UDP: {
// if (!flags.udp)
// return -1;
payloadSize -= sizeof(udphdr);
payload += sizeof(udphdr);
if (payloadSize < 0) {
std::cerr << "Invalid pcap UDP/IP packet" << std::endl;
return -1;
}
os << ", Tunnel Type: UDP";
break;
}
case IPPROTO_TCP: {
// if (!flags.tcp)
// return -1;
const tcphdr* tcpHeader = reinterpret_cast<const tcphdr*>(payload);
size_t tcpHeaderSize = TH_OFF(tcpHeader) * 4;
if (tcpHeaderSize < 20) {
std::cerr << "Invalid TCP Header len: " << tcpHeaderSize <<" bytes" << std::endl;
return -1;
}
payloadSize -= tcpHeaderSize;
payload += tcpHeaderSize;
if (payloadSize < 0) {
std::cerr << "Invalid pcap TCP/IP packet" << std::endl;
return -1;
}
os << ", Tunnel Type: TCP";
break;
}
default:
return -1;
}
break;
}
case /*ETHERTYPE_NDN*/0x7777:
os << "Tunnel Type: EthernetFrame";
break;
case /*ETHERTYPE_NDNLP*/0x8624:
os << "Tunnel Type: EthernetFrame";
break;
case 0x0077: // pcap
os << "Tunnel Type: PPP";
payloadSize -= 2;
payload += 2;
break;
default: // do nothing if it is not a recognized type of a packet
return -1;
}
return 0;
}
bool
Ndndump::matchesFilter(const Name& name) const
{
if (nameFilter.empty())
return true;
/// \todo Switch to NDN regular expressions
return boost::regex_match(name.toUri(), nameFilter);
}
} // namespace dump
} // namespace ndn