// -*- mode: c++; c-basic-offset: 4 -*-
/*
* fromtcpdump.{cc,hh} -- element reads packets from IP summary dump file
* Eddie Kohler
*
* Copyright (c) 2003 International Computer Science Institute
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, subject to the conditions
* listed in the Click LICENSE file. These conditions include: you must
* preserve this copyright notice, and you cannot mention the copyright
* holders in advertising related to the Software without their permission.
* The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
* notice is a summary of the Click LICENSE file; the license in that file is
* legally binding.
*/
#include <click/config.h>
#include "fromtcpdump.hh"
#include <click/confparse.hh>
#include <click/router.hh>
#include <click/standard/scheduleinfo.hh>
#include <click/error.hh>
#include <click/glue.hh>
#include <click/straccum.hh>
#include <clicknet/ip.h>
#include <clicknet/udp.h>
#include <click/packet_anno.hh>
#include <click/userutils.hh>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
CLICK_DECLS
FromTcpdump::FromTcpdump()
: _task(this)
{
_ff.set_landmark_pattern("%f:%l");
}
FromTcpdump::~FromTcpdump()
{
}
void *
FromTcpdump::cast(const char *n)
{
if (strcmp(n, Notifier::EMPTY_NOTIFIER) == 0 && !output_is_push(0)) {
_notifier.initialize(router());
return static_cast<Notifier *>(&_notifier);
} else
return Element::cast(n);
}
int
FromTcpdump::configure(Vector<String> &conf, ErrorHandler *errh)
{
bool stop = false, active = true, zero = true, checksum = false;
_sampling_prob = (1 << SAMPLING_SHIFT);
_absolute_seq = -1;
if (cp_va_parse(conf, this, errh,
cpFilename, "dump file name", &_ff.filename(),
cpKeywords,
"STOP", cpBool, "stop driver when done?", &stop,
"ACTIVE", cpBool, "start active?", &active,
"ZERO", cpBool, "zero packet data?", &zero,
"CHECKSUM", cpBool, "set packet checksums?", &checksum,
"SAMPLE", cpUnsignedReal2, "sampling probability", SAMPLING_SHIFT, &_sampling_prob,
cpEnd) < 0)
return -1;
if (_sampling_prob > (1 << SAMPLING_SHIFT)) {
errh->warning("SAMPLE probability reduced to 1");
_sampling_prob = (1 << SAMPLING_SHIFT);
} else if (_sampling_prob == 0)
errh->warning("SAMPLE probability is 0; emitting no packets");
_stop = stop;
_active = active;
_zero = zero;
_checksum = checksum;
_dead = false;
return 0;
}
int
FromTcpdump::initialize(ErrorHandler *errh)
{
// make sure notifier is initialized
if (!output_is_push(0))
_notifier.initialize(router());
if (_ff.initialize(errh) < 0)
return -1;
// read a line
String line;
if (_ff.peek_line(line, errh) < 0)
return -1;
else if (!line || !isdigit(line[0]))
errh->lwarning(_ff.print_filename(), "first line suspicious; is this a tcpdump output file?");
_format_complaint = false;
if (output_is_push(0))
ScheduleInfo::initialize_task(this, &_task, _active, errh);
return 0;
}
void
FromTcpdump::cleanup(CleanupStage)
{
_ff.cleanup();
}
static void
append_net_uint32_t(StringAccum &sa, uint32_t u)
{
sa << (char)(u >> 24) << (char)(u >> 16) << (char)(u >> 8) << (char)u;
}
static void
set_checksums(WritablePacket *q, click_ip *iph)
{
assert(iph == q->ip_header());
iph->ip_sum = 0;
iph->ip_sum = click_in_cksum((uint8_t *)iph, iph->ip_hl << 2);
if (IP_ISFRAG(iph))
/* nada */;
else if (iph->ip_p == IP_PROTO_TCP) {
click_tcp *tcph = q->tcp_header();
tcph->th_sum = 0;
unsigned csum = click_in_cksum((uint8_t *)tcph, q->transport_length());
tcph->th_sum = click_in_cksum_pseudohdr(csum, iph, q->transport_length());
} else if (iph->ip_p == IP_PROTO_UDP) {
click_udp *udph = q->udp_header();
udph->uh_sum = 0;
unsigned csum = click_in_cksum((uint8_t *)udph, q->transport_length());
udph->uh_sum = click_in_cksum_pseudohdr(csum, iph, q->transport_length());
}
}
const char *
FromTcpdump::read_tcp_line(WritablePacket *&q, const char *s, const char *end, int *data_len)
{
click_tcp *tcph = q->tcp_header();
// first, read flags
if (s < end && *s == '.')
tcph->th_flags = TH_ACK, s++;
else {
tcph->th_flags = 0;
while (s < end && IPSummaryDump::tcp_flag_mapping[(uint8_t) *s]) {
tcph->th_flags |= (1 << (IPSummaryDump::tcp_flag_mapping[ (uint8_t) *s ] - 1));
s++;
}
}
if (s >= end || *s != ' ')
return s;
// second, check for '[bad hdr length]' '[tcp sum ok]'
const char *s2;
if (s + 9 <= end && memcmp(s + 1, "[bad hdr", 8) == 0 && (s2 = find(s + 9, end, ']')) + 1 < end && s2[1] == ' ')
s = s2 + 1;
if (s + 14 <= end && memcmp(s + 1, "[tcp sum ok] ", 13) == 0)
s += 14;
else if (s + 9 <= end && memcmp(s + 1, "[bad tcp", 8) == 0 && (s2 = find(s + 9, end, ']')) + 1 < end && s2[1] == ' ')
s = s2 + 2;
else
s++;
// then read sequence numbers
uint32_t seq = 0, end_seq = 0, ack_seq = 0;
if (s < end && s[0] != 'a') {
const char *eseq = cp_unsigned(s, end, 0, &seq);
if (eseq == s || eseq >= end || *eseq != ':')
return s;
const char *eend_seq = cp_unsigned(eseq + 1, end, 0, &end_seq);
if (eend_seq == eseq + 1 || eend_seq >= end || *eend_seq != '(')
return s;
// skip parenthesized length
for (s = eend_seq + 1; s < end && isdigit(*s); s++)
/* nada */;
if (s >= end || *s != ')')
return s;
else if (s + 1 >= end || s[1] != ' ')
return s + 1;
else
s += 2;
}
*data_len = end_seq - seq;
// check for 'ack'
if (s + 4 < end && s[0] == 'a' && s[1] == 'c' && s[2] == 'k' && s[3] == ' ' && isdigit(s[4])) {
tcph->th_flags |= TH_ACK;
s = cp_unsigned(s + 4, end, 0, &ack_seq);
if (s < end && *s == ' ')
s++;
}
// patch sequence numbers
FlowRecord *record = 0;
bool rev = false;
if (tcph->th_flags & TH_ACK) {
// first, look up a record for this flow
const click_ip *iph = q->ip_header();
rev = (tcph->th_sport < tcph->th_dport
|| (tcph->th_sport == tcph->th_dport && iph->ip_src.s_addr < iph->ip_dst.s_addr));
if (rev)
record = _tcp_map.findp_force(IPFlowID(iph->ip_src, tcph->th_sport, iph->ip_dst, tcph->th_dport));
else
record = _tcp_map.findp_force(IPFlowID(iph->ip_dst, tcph->th_dport, iph->ip_src, tcph->th_sport));
// use tcpdump's heuristic for deciding when this is a new flow
if ((!record->init_seq[0] && !record->init_seq[1]) || (tcph->th_flags & TH_SYN)) {
record->init_seq[rev] = seq;
record->init_seq[!rev] = ack_seq - 1;
} else {
if (_absolute_seq < 0) // heuristic
_absolute_seq = (SEQ_GEQ(ack_seq, record->init_seq[!rev]) && ack_seq > 8000);
if (seq == 0 && end_seq == 0)
seq = end_seq = record->last_seq[rev];
else if (!_absolute_seq) {
seq += record->init_seq[rev];
end_seq += record->init_seq[rev];
}
if (!_absolute_seq)
ack_seq += record->init_seq[!rev];
}
// record last seen sequence numbers for assignment to pure acks
record->last_seq[rev] = end_seq + (tcph->th_flags & TH_SYN ? 1 : 0) + (tcph->th_flags & TH_FIN ? 1 : 0);
if (!record->last_seq[!rev])
record->last_seq[!rev] = ack_seq;
}
tcph->th_seq = htonl(seq);
tcph->th_ack = htonl(ack_seq);
// check for 'win'
uint32_t u;
if (s + 4 < end && s[0] == 'w' && s[1] == 'i' && s[2] == 'n' && s[3] == ' ' && isdigit(s[4])) {
s = cp_unsigned(s + 4, end, 0, &u); // XXX check u <= 65535
tcph->th_win = htons(u);
if (s < end && *s == ' ')
s++;
}
// check for 'urg'
if (s + 4 < end && s[0] == 'u' && s[1] == 'r' && s[2] == 'g' && s[3] == ' ' && isdigit(s[4])) {
s = cp_unsigned(s + 4, end, 0, &u); // XXX check u <= 65535
tcph->th_urp = htons(u);
if (s < end && *s != ' ')
s++;
}
// check for options
StringAccum opt;
if (s < end && s[0] == '<') {
for (s++; s < end && *s != '>'; ) {
int optlen1 = opt.length();
if (s + 3 <= end && s[0] == 'n' && s[1] == 'o' && s[2] == 'p') {
opt << (char)TCPOPT_NOP;
s += 3;
} else if (s + 3 <= end && s[0] == 'e' && s[1] == 'o' && s[2] == 'l') {
opt << (char)TCPOPT_EOL;
s += 3;
} else if (s + 4 < end && s[0] == 'm' && s[1] == 's' && s[2] == 's' && s[3] == ' ' && isdigit(s[4])) {
s = cp_unsigned(s + 4, end, 0, &u); // XXX check u <= 65535
opt << (char)TCPOPT_MAXSEG << (char)TCPOLEN_MAXSEG << (char)((u >> 8) & 255) << (char)(u & 255);
} else if (s + 7 < end && memcmp(s, "wscale ", 7) == 0 && isdigit(s[7])) {
s = cp_unsigned(s + 7, end, 0, &u); // XXX check u <= 255
opt << (char)TCPOPT_WSCALE << (char)TCPOLEN_WSCALE << (char)u;
} else if (s + 6 <= end && memcmp(s, "sackOK", 6) == 0) {
opt << (char)TCPOPT_SACK_PERMITTED << (char)TCPOLEN_SACK_PERMITTED;
s += 6;
} else if (s + 10 < end && memcmp(s, "timestamp ", 10) == 0 && isdigit(s[10])) {
s = cp_unsigned(s + 10, end, 0, &u);
if (s + 1 < end && *s == ' ' && isdigit(s[1])) {
uint32_t u2;
s = cp_unsigned(s + 1, end, 0, &u2);
opt << (char)TCPOPT_TIMESTAMP << (char)TCPOLEN_TIMESTAMP;
append_net_uint32_t(opt, u);
append_net_uint32_t(opt, u2);
}
} else if (s + 10 < end && memcmp(s, "sack sack ", 10) == 0 && isdigit(s[10])) {
uint32_t nsack, u2;
s = cp_unsigned(s + 10, end, 0, &nsack);
opt << (char)TCPOPT_SACK << (char)(nsack * 8 + 2);
while (s < end && *s == ' ')
s++;
while (s + 1 < end && *s == '{' && isdigit(s[1])) {
s = cp_unsigned(s + 1, end, 0, &u);
if (s + 1 < end && *s == ':' && isdigit(s[1])) {
s = cp_unsigned(s + 1, end, 0, &u2);
if (s < end && *s == '}') {
s++;
if (record && !_absolute_seq) {
u += record->init_seq[!rev];
u2 += record->init_seq[!rev];
}
append_net_uint32_t(opt, u);
append_net_uint32_t(opt, u2);
}
}
}
while (s < end && *s == ' ')
s++;
}
if (s < end && *s == ',')
s++;
else if (s < end && *s != '>') { // not the option we thought
opt.set_length(optlen1);
while (s < end && *s != ',' && *s != '>')
s++;
}
}
if (s < end && *s == '>')
s++;
while (opt.length() % 4 != 0)
opt << (char)TCPOPT_EOL;
q = q->put(opt.length());
memcpy(q->transport_header() + sizeof(click_tcp), opt.data(), opt.length());
q->tcp_header()->th_off = (sizeof(click_tcp) + opt.length()) >> 2;
} else
tcph->th_off = sizeof(click_tcp) >> 2;
return s;
}
const char *
FromTcpdump::read_udp_line(WritablePacket *&, const char *s, const char *end, int *data_len)
{
// first, check for '[bad hdr length]' '[udp sum ok]'
const char *s2;
if (s + 9 <= end && memcmp(s + 1, "[bad hdr", 8) == 0 && (s2 = find(s + 9, end, ']')) + 1 < end && s2[1] == ' ')
s = s2 + 1;
if (s + 14 <= end && memcmp(s + 1, "[udp sum ok] ", 13) == 0)
s += 14;
else if (s + 9 <= end && memcmp(s + 1, "[bad udp", 8) == 0 && (s2 = find(s + 9, end, ']')) + 1 < end && s2[1] == ' ')
s = s2 + 2;
// then check for 'udp LENGTH'
if (s + 4 < end && s[0] == 'u' && s[1] == 'd' && s[2] == 'p' && s[3] == ' ' && isdigit(s[4])) {
uint32_t dl;
s = cp_unsigned(s + 4, end, 0, &dl);
*data_len = dl;
}
return s;
}
Packet *
FromTcpdump::read_packet(ErrorHandler *errh)
{
WritablePacket *q = Packet::make(0, (const unsigned char *)0, sizeof(click_ip) + sizeof(click_tcp), 20);
if (!q) {
_ff.error(errh, "out of memory!");
_dead = true;
return 0;
}
if (_zero)
memset(q->data(), 0, q->length());
q->set_ip_header((click_ip *)q->data(), sizeof(click_ip));
click_ip *iph = q->ip_header();
iph->ip_v = 4;
iph->ip_hl = sizeof(click_ip) >> 2;
iph->ip_off = 0;
iph->ip_tos = 0;
iph->ip_ttl = 2;
click_udp *udph = q->udp_header();
String line;
StringAccum payload;
String ip_opt;
String tcp_opt;
while (1) {
if (_ff.read_line(line, errh) <= 0) {
q->kill();
_dead = true;
return 0;
}
const char *s = line.data();
const char *end = line.end();
if (s >= end || s[0] == '#')
continue;
else if (!isdigit(s[0]))
break;
// first, read timestamp
const char *s2 = find(s, end, ' ');
if (!cp_time(line.substring(s, s2), &q->timestamp_anno()))
break;
s = s2 + 1;
// then, guess protocol
iph->ip_p = 0;
const char *colon = find(s, end, ':');
for (colon++; colon < end && *colon == ' '; colon++)
/* nada */;
if (colon < end) {
if (*colon == 'i')
iph->ip_p = IP_PROTO_ICMP;
else if (*colon == 'u' || (*colon == '[' && colon + 1 < end && colon[1] == 'u'))
iph->ip_p = IP_PROTO_UDP;
else if (*colon == '.' || (*colon >= 'A' && *colon <= 'Z'))
iph->ip_p = IP_PROTO_TCP;
}
// then, read source IP address and port
s2 = find(s, end, ' ');
if (s2 == s || s2 + 2 >= end || s2[1] != '>' || s2[2] != ' ')
break;
if (iph->ip_p == IP_PROTO_TCP || iph->ip_p == IP_PROTO_UDP) {
const char *sm = s2 - 1;
while (sm > s && *sm != '.' && *sm != ':')
sm--;
if (!cp_ip_address(line.substring(s, sm), &iph->ip_src)
|| !cp_tcpudp_port(line.substring(sm + 1, s2), iph->ip_p, &udph->uh_sport))
break;
else
udph->uh_sport = htons(udph->uh_sport);
} else if (!cp_ip_address(line.substring(s, s2), &iph->ip_src))
break;
s = s2 + 3;
// then, read destination IP address and port
s2 = find(s, end, ':');
if (iph->ip_p == IP_PROTO_TCP || iph->ip_p == IP_PROTO_UDP) {
const char *sm = s2 - 1;
while (sm > s && *sm != '.' && *sm != ':')
sm--;
if (!cp_ip_address(line.substring(s, sm), &iph->ip_dst)
|| !cp_tcpudp_port(line.substring(sm + 1, s2), iph->ip_p, &udph->uh_dport))
break;
else
udph->uh_dport = htons(udph->uh_dport);
} else if (!cp_ip_address(line.substring(s, s2), &iph->ip_dst))
break;
// then, read protocol data
int data_len = -1;
if (iph->ip_p == IP_PROTO_TCP && IP_FIRSTFRAG(iph)) {
s = read_tcp_line(q, colon, end, &data_len);
iph = q->ip_header();
} else if (iph->ip_p == IP_PROTO_UDP && IP_FIRSTFRAG(iph)) {
s = read_udp_line(q, colon, end, &data_len);
q->take(sizeof(click_tcp) - sizeof(click_udp));
} else {
q->take(sizeof(click_tcp));
s = colon;
}
// parse IP stuff at the end of the line
// TTL and ID
s2 = end - 1;
while (s2 > s) {
while (s2 > s && isspace(*s2))
s2--;
if (s2 <= s || (*s2 != ')' && *s2 != ']'))
break;
char opener = (*s2 == ')' ? '(' : '[');
const char *open = s2 - 1;
while (open >= s && *open != opener)
open--;
const char *close = s2;
s2 = open - 1;
uint32_t u;
if (open >= s && open < close) {
const char *item = open + 1;
while (item < close) {
if (close - item >= 7 && memcmp(item, "tos 0x", 6) == 0) {
item = cp_unsigned(item + 6, close, 16, &u);
iph->ip_tos = u;
} else if (close - item >= 6 && memcmp(item, "ECT(", 4) == 0 && (item[4] == '0' || item[4] == '1') && item[5] == ')') {
iph->ip_tos = (iph->ip_tos & ~IP_ECNMASK) | (item[4] == '0' ? IP_ECN_ECT1 : IP_ECN_ECT2);
item += 6;
} else if (close - item >= 2 && item[0] == 'C' && item[1] == 'E') {
iph->ip_tos = (iph->ip_tos & ~IP_ECNMASK) | IP_ECN_CE;
item += 2;
} else if (close - item >= 2 && item[0] == 'D' && item[1] == 'F') {
iph->ip_off |= htons(IP_DF);
item += 2;
} else if (close - item >= 10 && memcmp(item, "frag ", 5) == 0 && isdigit(item[5])) {
item = cp_unsigned(item + 5, close, 0, &u);
iph->ip_id = htons(u);
if (item > close - 2 || *item != ':' || !isdigit(item[1]))
break;
item = cp_unsigned(item + 1, close, 0, &u);
data_len = u;
if (item > close - 2 || *item != '@' || !isdigit(item[1]))
break;
item = cp_unsigned(item + 1, close, 0, &u);
iph->ip_off = (iph->ip_off & htons(~IP_OFFMASK)) | htons(u);
if (item < close && *item == '+')
iph->ip_off |= htons(IP_MF), item++;
} else if (close - item >= 5 && memcmp(item, "ttl ", 4) == 0 && isdigit(item[4])) {
item = cp_unsigned(item + 4, close, 0, &u);
iph->ip_ttl = u;
} else if (close - item >= 4 && memcmp(item, "id ", 3) == 0 && isdigit(item[3])) {
item = cp_unsigned(item + 3, close, 0, &u);
iph->ip_id = htons(u);
} else if (close - item >= 5 && memcmp(item, "len ", 4) == 0 && isdigit(item[4])) {
item = cp_unsigned(item + 4, close, 0, &u);
if (data_len < 0 || u == q->length() + data_len)
data_len = u - q->length();
else if (iph->ip_p == IP_PROTO_TCP) {
// the discrepancy must be due to TCP options
int delta = u - (q->length() + data_len);
q->change_headroom_and_length(q->headroom(), u - data_len);
q->tcp_header()->th_off = (q->transport_length() >> 2);
if (delta > 0)
q->end_data()[-delta] = TCPOPT_EOL;
}
} else
break;
while (item < close && (*item == ',' || isspace(*item)))
item++;
}
}
}
// set IP length
if (data_len < 0)
data_len = 0;
iph->ip_len = ntohs(q->length() + data_len);
SET_EXTRA_LENGTH_ANNO(q, data_len);
// set UDP length (after IP length is available)
if (iph->ip_p == IP_PROTO_UDP && IP_FIRSTFRAG(iph))
q->udp_header()->uh_ulen = htons(ntohs(iph->ip_len) - (iph->ip_hl << 2));
// set checksum
if (_checksum)
set_checksums(q, iph);
return q;
}
// bad format if we get here
if (!_format_complaint) {
// don't complain if the line was all blank
const char *s = line.begin();
while (s < line.end() && (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r'))
s++;
if (s != line.end()) {
_ff.error(errh, "packet parse error");
_format_complaint = true;
}
}
if (q)
q->kill();
return 0;
}
bool
FromTcpdump::run_task()
{
if (!_active)
return false;
Packet *p;
while (1) {
p = read_packet(0);
if (_dead) {
if (_stop)
router()->please_stop_driver();
return false;
}
// check sampling probability
if (_sampling_prob >= (1 << SAMPLING_SHIFT)
|| (uint32_t)(random() & ((1 << SAMPLING_SHIFT) - 1)) < _sampling_prob)
break;
if (p)
p->kill();
}
if (p)
output(0).push(p);
_task.fast_reschedule();
return true;
}
Packet *
FromTcpdump::pull(int)
{
if (!_active)
return 0;
Packet *p;
while (1) {
p = read_packet(0);
if (_dead) {
if (_stop)
router()->please_stop_driver();
_notifier.sleep();
return 0;
}
// check sampling probability
if (_sampling_prob >= (1 << SAMPLING_SHIFT)
|| (uint32_t)(random() & ((1 << SAMPLING_SHIFT) - 1)) < _sampling_prob)
break;
if (p)
p->kill();
}
_notifier.wake();
return p;
}
enum { H_SAMPLING_PROB, H_ACTIVE, H_ENCAP, H_STOP };
String
FromTcpdump::read_handler(Element *e, void *thunk)
{
FromTcpdump *fd = static_cast<FromTcpdump *>(e);
switch ((intptr_t)thunk) {
case H_SAMPLING_PROB:
return cp_unparse_real2(fd->_sampling_prob, SAMPLING_SHIFT);
case H_ACTIVE:
return cp_unparse_bool(fd->_active);
case H_ENCAP:
return "IP";
default:
return "<error>";
}
}
int
FromTcpdump::write_handler(const String &s_in, Element *e, void *thunk, ErrorHandler *errh)
{
FromTcpdump *fd = static_cast<FromTcpdump *>(e);
String s = cp_uncomment(s_in);
switch ((intptr_t)thunk) {
case H_ACTIVE: {
bool active;
if (cp_bool(s, &active)) {
fd->_active = active;
if (fd->output_is_push(0) && active && !fd->_task.scheduled())
fd->_task.reschedule();
else if (!fd->output_is_push(0))
fd->_notifier.set_active(active, true);
return 0;
} else
return errh->error("`active' should be Boolean");
}
case H_STOP:
fd->_active = false;
fd->router()->please_stop_driver();
return 0;
default:
return -EINVAL;
}
}
void
FromTcpdump::add_handlers()
{
add_read_handler("sampling_prob", read_handler, (void *)H_SAMPLING_PROB);
add_read_handler("active", read_handler, (void *)H_ACTIVE);
add_write_handler("active", write_handler, (void *)H_ACTIVE);
add_read_handler("encap", read_handler, (void *)H_ENCAP);
add_write_handler("stop", write_handler, (void *)H_STOP);
_ff.add_handlers(this);
if (output_is_push(0))
add_task_handlers(&_task);
}
ELEMENT_REQUIRES(userlevel FromFile IPSummaryDump)
EXPORT_ELEMENT(FromTcpdump)
#include <click/hashmap.cc>
CLICK_ENDDECLS
syntax highlighted by Code2HTML, v. 0.9.1