315 lines
13 KiB
C++
315 lines
13 KiB
C++
#include "ipv4_datagram.hh"
|
|
#include "ipv4_header.hh"
|
|
#include "tcp_header.hh"
|
|
#include "tcp_segment.hh"
|
|
#include "test_utils.hh"
|
|
#include "test_utils_ipv4.hh"
|
|
#include "util.hh"
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <exception>
|
|
#include <iostream>
|
|
#include <pcap/pcap.h>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace std;
|
|
|
|
constexpr unsigned NREPS = 32;
|
|
|
|
uint16_t inet_cksum(const uint8_t *data, const size_t len) {
|
|
InternetChecksum check;
|
|
check.add({reinterpret_cast<const char *>(data), len});
|
|
return check.value();
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
try {
|
|
// first, make sure the parser gets the correct values and catches errors
|
|
auto rd = get_random_generator();
|
|
for (unsigned i = 0; i < NREPS; ++i) {
|
|
const uint16_t totlen = 30 + rd() % 1024;
|
|
vector<uint8_t> test_header(totlen, 0);
|
|
generate(test_header.begin(), test_header.end(), [&] { return rd(); });
|
|
test_header[0] = 0x45; // IPv4, length 20
|
|
test_header[2] = totlen >> 8;
|
|
test_header[3] = totlen & 0xff;
|
|
test_header[14] = test_header[15] = 0;
|
|
const uint16_t cksum = inet_cksum(test_header.data(), 20);
|
|
test_header[14] = cksum >> 8;
|
|
test_header[15] = cksum & 0xff;
|
|
|
|
IPv4Header test_hdr{};
|
|
{
|
|
NetParser p{string(test_header.begin(), test_header.end())};
|
|
if (auto ret = test_hdr.parse(p); ret != ParseResult::NoError) {
|
|
throw runtime_error("Parse error: " + as_string(ret));
|
|
}
|
|
}
|
|
if (test_hdr.tos != test_header[1]) {
|
|
throw runtime_error("Parse error: tos field is wrong");
|
|
}
|
|
if (test_hdr.len != totlen) {
|
|
throw runtime_error("Parse error: len field is wrong");
|
|
}
|
|
if (uint16_t tval = (test_header[4] << 8) | test_header[5]; test_hdr.id != tval) {
|
|
throw runtime_error("Parse error: id field is wrong");
|
|
}
|
|
if (uint16_t tval = ((test_header[6] & 0x7f) << 8) | test_header[7];
|
|
tval != ((test_hdr.df ? 0x4000 : 0) | (test_hdr.mf ? 0x2000 : 0) | test_hdr.offset)) {
|
|
throw runtime_error("Parse error: flags or fragment offset is wrong");
|
|
}
|
|
if (test_header[8] != test_hdr.ttl) {
|
|
throw runtime_error("Parse error: ttl is wrong");
|
|
}
|
|
if (test_header[9] != test_hdr.proto) {
|
|
throw runtime_error("Parse error: proto is wrong");
|
|
}
|
|
if (uint16_t tval = (test_header[10] << 8) | test_header[11]; test_hdr.cksum != tval) {
|
|
throw runtime_error("Parse error: cksum is wrong");
|
|
}
|
|
if (uint32_t tval =
|
|
(test_header[12] << 24) | (test_header[13] << 16) | (test_header[14] << 8) | test_header[15];
|
|
test_hdr.src != tval) {
|
|
throw runtime_error("Parse error: src addr is wrong");
|
|
}
|
|
if (uint32_t tval =
|
|
(test_header[16] << 24) | (test_header[17] << 16) | (test_header[18] << 8) | test_header[19];
|
|
test_hdr.dst != tval) {
|
|
throw runtime_error("Parse error: dst addr is wrong");
|
|
}
|
|
|
|
test_header[0] = 0x55;
|
|
{
|
|
const uint16_t new_cksum = inet_cksum(test_header.data(), 20);
|
|
test_header[14] = new_cksum >> 8;
|
|
test_header[15] = new_cksum & 0xff;
|
|
NetParser p{string(test_header.begin(), test_header.end())};
|
|
if (auto ret = test_hdr.parse(p); ret != ParseResult::WrongIPVersion) {
|
|
throw runtime_error("Parse error: failed to detect wrong IP version.");
|
|
}
|
|
}
|
|
|
|
test_header[0] = 0x44;
|
|
{
|
|
const uint16_t new_cksum = inet_cksum(test_header.data(), 20);
|
|
test_header[14] = new_cksum >> 8;
|
|
test_header[15] = new_cksum & 0xff;
|
|
NetParser p{string(test_header.begin(), test_header.end())};
|
|
if (auto ret = test_hdr.parse(p); ret != ParseResult::HeaderTooShort) {
|
|
throw runtime_error("Parse error: failed to detect header too short");
|
|
}
|
|
}
|
|
|
|
test_header[0] = 0x45;
|
|
test_header[14] = (cksum >> 8) + max(int(rd()), 1);
|
|
test_header[15] = (cksum & 0xff) + max(int(rd()), 1);
|
|
{
|
|
NetParser p{string(test_header.begin(), test_header.end())};
|
|
if (auto ret = test_hdr.parse(p); ret != ParseResult::BadChecksum) {
|
|
throw runtime_error("Parse error: failed to detect incorrect checksum");
|
|
}
|
|
}
|
|
|
|
test_header.resize(totlen - 10);
|
|
{
|
|
NetParser p{string(test_header.begin(), test_header.end())};
|
|
if (auto ret = test_hdr.parse(p); ret != ParseResult::TruncatedPacket) {
|
|
throw runtime_error("Parse error: failed to detect truncated packet");
|
|
}
|
|
}
|
|
|
|
test_header[0] = 0x46;
|
|
test_header.resize(20);
|
|
{
|
|
const uint16_t new_cksum = inet_cksum(test_header.data(), 20);
|
|
test_header[14] = new_cksum >> 8;
|
|
test_header[15] = new_cksum & 0xff;
|
|
NetParser p{string(test_header.begin(), test_header.end())};
|
|
if (auto ret = test_hdr.parse(p); ret != ParseResult::PacketTooShort) {
|
|
throw runtime_error("Parse error: failed to detect packet too short to parse");
|
|
}
|
|
}
|
|
|
|
test_header[0] = 0x45;
|
|
test_header[14] = (cksum >> 8) + max(int(rd()), 1);
|
|
test_header[15] = (cksum & 0xff) + max(int(rd()), 1);
|
|
test_header.resize(16);
|
|
{
|
|
NetParser p{string(test_header.begin(), test_header.end())};
|
|
if (auto ret = test_hdr.parse(p); ret != ParseResult::PacketTooShort) {
|
|
throw runtime_error("Parse error: failed to detect packet too short to parse");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (argc < 2) {
|
|
cout << "USAGE: " << argv[0] << " <filename>" << endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
char errbuf[PCAP_ERRBUF_SIZE];
|
|
pcap_t *pcap = pcap_open_offline(argv[1], static_cast<char *>(errbuf));
|
|
if (pcap == nullptr) {
|
|
cout << "ERROR opening " << argv[1] << ": " << static_cast<char *>(errbuf) << endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (pcap_datalink(pcap) != 1) {
|
|
cout << "ERROR expected ethernet linktype in capture file" << endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
bool ok = true;
|
|
const uint8_t *pkt;
|
|
struct pcap_pkthdr hdr;
|
|
while ((pkt = pcap_next(pcap, &hdr)) != nullptr) {
|
|
if (hdr.caplen < 14) {
|
|
cout << "ERROR frame too short to contain Ethernet header\n";
|
|
ok = false;
|
|
continue;
|
|
}
|
|
|
|
const bool expect_fail = (pkt[12] != 0x08) || (pkt[13] != 0x00);
|
|
IPv4Datagram ip_dgram;
|
|
if (auto res = ip_dgram.parse(string(pkt + 14, pkt + hdr.caplen)); res != ParseResult::NoError) {
|
|
// parse failed
|
|
if (expect_fail) {
|
|
continue;
|
|
}
|
|
|
|
auto ip_parse_result = as_string(res);
|
|
cout << "ERROR got unexpected IP parse failure " << ip_parse_result << " for this datagram:\n";
|
|
show_ethernet_frame(pkt, hdr);
|
|
hexdump(pkt + 14, hdr.caplen - 14);
|
|
ok = false;
|
|
continue;
|
|
}
|
|
|
|
TCPSegment tcp_seg;
|
|
if (auto res = tcp_seg.parse(ip_dgram.payload(), ip_dgram.header().pseudo_cksum());
|
|
res != ParseResult::NoError) {
|
|
// parse failed
|
|
if (expect_fail) {
|
|
continue;
|
|
}
|
|
|
|
auto tcp_parse_result = as_string(res);
|
|
cout << "ERROR got unexpected TCP parse failure " << tcp_parse_result << " for this segment:\n";
|
|
show_ethernet_frame(pkt, hdr);
|
|
hexdump(pkt + 14, hdr.caplen - 14);
|
|
ok = false;
|
|
continue;
|
|
}
|
|
|
|
if (expect_fail) {
|
|
cout << "ERROR: expected parse failure but got success. Something is wrong.\n";
|
|
show_ethernet_frame(pkt, hdr);
|
|
hexdump(pkt + 14, hdr.caplen - 14);
|
|
ok = false;
|
|
continue;
|
|
}
|
|
|
|
// parse succeeded. Create a new packet and rebuild the header by unparsing.
|
|
cout << dec;
|
|
|
|
IPv4Datagram ip_dgram_copy;
|
|
TCPSegment tcp_seg_copy;
|
|
tcp_seg_copy.payload() = tcp_seg.payload();
|
|
|
|
// set headers in new packets, and fix up to remove extensions
|
|
{
|
|
const IPv4Header &ipv4_hdr_orig = ip_dgram.header();
|
|
IPv4Header &ipv4_hdr_copy = ip_dgram_copy.header();
|
|
ipv4_hdr_copy = ipv4_hdr_orig;
|
|
|
|
const TCPHeader &tcp_hdr_orig = tcp_seg.header();
|
|
TCPHeader &tcp_hdr_copy = tcp_seg_copy.header();
|
|
tcp_hdr_copy = tcp_hdr_orig;
|
|
|
|
// fix up packets to remove IPv4 and TCP header extensions
|
|
ipv4_hdr_copy.len -= 4 * ipv4_hdr_orig.hlen - IPv4Header::LENGTH;
|
|
ipv4_hdr_copy.hlen = 5;
|
|
ipv4_hdr_copy.len -= 4 * tcp_hdr_orig.doff - TCPHeader::LENGTH;
|
|
tcp_hdr_copy.doff = 5;
|
|
} // ipv4_hdr_{orig,copy}, tcp_hdr_{orig,copy} go out of scope
|
|
|
|
if (!compare_ip_headers_nolen(ip_dgram.header(), ip_dgram_copy.header())) {
|
|
cout << "ERROR: after unparsing, IP headers (other than length) don't match.\n";
|
|
}
|
|
if (!compare_tcp_headers_nolen(tcp_seg.header(), tcp_seg_copy.header())) {
|
|
cout << "ERROR: after unparsing, TCP headers (other than length) don't match.\n";
|
|
}
|
|
|
|
// create a new datagram from the serialized IP and TCP headers + payload
|
|
IPv4Datagram ip_dgram_copy2;
|
|
|
|
string concat;
|
|
concat.append(ip_dgram_copy.header().serialize());
|
|
concat.append(tcp_seg_copy.serialize(ip_dgram_copy.header().pseudo_cksum()).concatenate());
|
|
|
|
if (auto res = ip_dgram_copy2.parse(string(concat)); res != ParseResult::NoError) {
|
|
auto ip_parse_result = as_string(res);
|
|
cout << "ERROR got IP parse failure " << ip_parse_result << " for this datagram (copy2):\n";
|
|
cout << ip_dgram_copy.header().to_string();
|
|
hexdump(concat.data(), concat.size());
|
|
cout << endl;
|
|
hexdump(pkt + 14, hdr.caplen - 14);
|
|
ok = false;
|
|
continue;
|
|
}
|
|
|
|
TCPSegment tcp_seg_copy2;
|
|
if (auto res = tcp_seg_copy2.parse(ip_dgram_copy2.payload(), ip_dgram_copy2.header().pseudo_cksum());
|
|
res != ParseResult::NoError) {
|
|
auto tcp_parse_result = as_string(res);
|
|
cout << "ERROR got TCP parse failure " << tcp_parse_result << " for this segment (copy2):\n";
|
|
cout << tcp_seg_copy.header().to_string();
|
|
ok = false;
|
|
continue;
|
|
}
|
|
|
|
if (!compare_ip_headers_nolen(ip_dgram.header(), ip_dgram_copy2.header())) {
|
|
cout << "ERROR: after re-parsing, IP headers don't match (0<->2).\n";
|
|
ok = false;
|
|
continue;
|
|
}
|
|
if (!compare_ip_headers(ip_dgram_copy.header(), ip_dgram_copy2.header())) {
|
|
cout << "ERROR: after re-parsing, IP headers don't match (1<->2).\n";
|
|
ok = false;
|
|
continue;
|
|
}
|
|
if (!compare_tcp_headers_nolen(tcp_seg.header(), tcp_seg_copy2.header())) {
|
|
cout << "ERROR: after re-parsing, TCP headers don't match (0<->2).\n";
|
|
ok = false;
|
|
continue;
|
|
}
|
|
if (!compare_tcp_headers(tcp_seg_copy.header(), tcp_seg_copy2.header())) {
|
|
cout << "ERROR: after re-parsing, TCP headers don't match (1<->2).\n";
|
|
ok = false;
|
|
continue;
|
|
}
|
|
if (tcp_seg_copy.payload().str() != tcp_seg_copy2.payload().str()) {
|
|
cout << "ERROR: after re-parsing, TCP payloads don't match.\n";
|
|
hexdump(tcp_seg_copy2.payload().str().data(), tcp_seg_copy2.payload().str().size());
|
|
cout << endl;
|
|
hexdump(tcp_seg_copy.payload().str().data(), tcp_seg_copy.payload().str().size());
|
|
ok = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
pcap_close(pcap);
|
|
if (!ok) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
} catch (const exception &e) {
|
|
cout << "Exception: " << e.what() << endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|