First release of open-appsec source code

This commit is contained in:
roybarda
2022-10-26 19:33:19 +03:00
parent 3883109caf
commit a883352f79
1353 changed files with 276290 additions and 1 deletions

View File

@@ -0,0 +1,10 @@
if("${PLATFORM_TYPE}" MATCHES "arm.*")
ADD_DEFINITIONS(-Wno-sign-compare)
elseif("${PLATFORM_TYPE}" STREQUAL "alpine")
ADD_DEFINITIONS(-Wno-deprecated-copy)
endif()
include_directories(${CMAKE_SOURCE_DIR}/components/include)
include_directories(${gtest_INCLUDE_DIRS})
add_library(cptest cptest.cc cptest_data_buf.cc cptest_tcppacket.cc)
add_subdirectory(cptest_ut)

145
core/cptest/cptest.cc Executable file
View File

@@ -0,0 +1,145 @@
#include "cptest.h"
#include <iostream>
#include <string>
#include <vector>
#include <fcntl.h>
#include "gtest/gtest.h"
#include "debug.h"
using namespace std;
void
cptestPrepareToDie()
{
Debug::setNewDefaultStdout(&std::cerr); // EXPECT_DEATH checks cerror for messages, so send them there.
}
CPTestTempfile::CPTestTempfile(const std::vector<std::string> &lines)
{
// Create the file
char temp_file_template[] = "/tmp/cptest_temp_file_XXXXXX";
int fd = mkstemp(temp_file_template);
if (fd < 0) {
// LCOV_EXCL_START - mkstemp rarely fails, can't cause it.
ADD_FAILURE() << "Failed to open tempfile";
return;
// LCOV_EXCL_STOP
}
fname = temp_file_template; // Template was modified to actual file name
// Fill it with the requested lines
for (const auto &l : lines) {
EXPECT_EQ((unsigned long)write(fd, l.c_str(), l.size()), l.size());
EXPECT_EQ(write(fd, "\n", 1), 1);
}
close(fd);
}
CPTestTempfile::CPTestTempfile()
:
CPTestTempfile(std::vector<std::string>{})
{
}
CPTestTempfile::~CPTestTempfile()
{
if (!fname.empty()) {
unlink(fname.c_str());
}
}
string
CPTestTempfile::readFile() const
{
int fd = open(fname.c_str(), 0);
EXPECT_NE(fd, -1);
string result;
char buf[100];
while (true) {
auto bytes_read = read(fd, buf, 100);
if (bytes_read <= 0) break;
result += string(buf, bytes_read);
}
return result;
}
// Parse Hex data, e.g. what's generated by "tcpdump -xx", into a vector
vector<u_char>
cptestParseHex(const string &hex_text)
{
vector<u_char> v;
// Use stringstream and istream_iterator to break the input into whitespace separated strings
stringstream str(hex_text);
for (auto it = istream_iterator<string>(str); it != istream_iterator<string>(); it++) {
const string &t = *it;
size_t l = t.size();
if (l==0) continue;
if (t[l-1]==':') continue; // tcpdump uses xxxx: to mark offsets, not data. So ignore it.
dbgAssert(t.size() %2 == 0) << "Expecting an even number of hex digits, " << t << " is invalid";
for (uint i=0; i<t.size()/2; i++) {
u_char n = strtoul(t.substr(i*2, 2).c_str(), nullptr, 16);
v.push_back(n);
}
}
return v;
}
// The inverse of cptest_parse_hex
// Take a vector of data, and generate hex from it output, like tcpdump.
std::string
cptestGenerateHex(const std::vector<u_char> &vec, bool print_offsets)
{
std::string res;
int total_hex_groups_emitted = 0;
int num_hex_groups_emitted_in_cur_line = 0;
for (size_t i = 0; i<vec.size(); i++) {
if (num_hex_groups_emitted_in_cur_line == 16) {
res += "\n";
num_hex_groups_emitted_in_cur_line = 0;
}
if (print_offsets && (num_hex_groups_emitted_in_cur_line == 0)) {
char offset_str[12];
snprintf(offset_str, sizeof(offset_str), "%04x: ", total_hex_groups_emitted);
res += offset_str;
}
char cur_char_as_hex[10];
snprintf(cur_char_as_hex, sizeof(cur_char_as_hex), "%02x ", vec[i]);
res += cur_char_as_hex;
num_hex_groups_emitted_in_cur_line++;
total_hex_groups_emitted++;
}
return res;
}
// Get the path to a file, which is in the same directory as the binary.
std::string
cptestFnameInExeDir(const std::string &name)
{
auto bin_path = ::testing::internal::GetArgvs()[0]; // Internal ugly API.
auto slash = bin_path.rfind('/');
if (slash==string::npos) {
// bin_path contains no dir. So return name with no dir
// LCOV_EXCL_START - Our unit tests always run from an absolute path
return name;
// LCOV_EXCL_STOP
}
auto bin_dir = bin_path.substr(0, slash);
return bin_dir + "/" + name;
}
string
cptestFnameInSrcDir(const string &name)
{
char *path = getenv("CURR_SRC_DIR");
if (path == nullptr) return name;
return std::string(path) + "/" + name;
}

48
core/cptest/cptest_data_buf.cc Executable file
View File

@@ -0,0 +1,48 @@
#include <iostream>
#include <string>
#include <vector>
#include "cptest.h"
#include "gtest/gtest.h"
#include "debug.h"
using namespace std;
static string
formatNum(const char *fmt, int n)
{
char buf[100];
snprintf(buf, sizeof(buf), fmt, n);
return string(buf);
}
ostream &
operator<<(ostream &os, const Buffer &buf)
{
auto len = buf.size();
auto data = buf.data();
const int line_chars = 16;
os << "Buffer Data:" << endl;
for (uint i = 0; i<(len+line_chars-1)/line_chars; i++) {
// Line header
os << formatNum("%04x", i*line_chars) << ": ";
// Hex of each character
for (uint j = 0; j<line_chars; j++) {
uint pos = i*line_chars + j;
os << " " << (pos<len ? formatNum("%02x", data[pos]) : " ");
}
os << " ";
// Printable chars
for (uint j = 0; j<line_chars; j++) {
uint pos = i*line_chars + j;
if (pos >= len) break;
os << (isprint(data[pos]) ? char(data[pos]) : '.');
}
os << endl;
}
return os;
}

593
core/cptest/cptest_tcppacket.cc Executable file
View File

@@ -0,0 +1,593 @@
#include "cptest/cptest_tcppacket.h"
#include "cptest.h"
#include "c_common/network_defs.h"
#include "packet.h"
using namespace std;
USE_DEBUG_FLAG(D_STREAMING);
//
// Append some data - various overloads - to a u_char vector
//
void
vec_append(vector<u_char> &target, const void *data, uint len)
{
auto p = reinterpret_cast<const u_char *>(data);
target.insert(target.end(), p, p + len);
}
void
vec_append(vector<u_char> &target, uint num)
{
vec_append(target, &num, sizeof(num));
}
void
vec_append(vector<u_char> &target, const vector<u_char> &source)
{
target.insert(target.end(), source.begin(), source.end());
}
//
// TCP Option generation
//
class TCPOption::Impl
{
public:
explicit Impl(const string &_name, const vector<u_char> &_data);
const string name;
const vector<u_char> data;
};
TCPOption::Impl::Impl(const string &_name, const vector<u_char> &_data)
:
name(_name),
data(_data)
{
}
TCPOption::TCPOption(const string &_name, const vector<u_char> _data)
:
pimpl(make_unique<Impl>(_name, _data))
{
}
TCPOption::TCPOption(const TCPOption &from)
:
pimpl(make_unique<Impl>(*from.pimpl))
{
}
TCPOption::~TCPOption()
{
}
size_t
TCPOption::size() const
{
return pimpl->data.size();
}
vector<u_char>
TCPOption::build() const
{
return pimpl->data;
}
const TCPOption TCPOption::NOP("NOP", { 1 }); // Type 1, no length (exceptional)
const TCPOption TCPOption::SACK_PERMITTED("sack permitted", { 4, 2 }); // Type 4, length 2
TCPOption
TCPOption::windowScaling(u_char shift_count)
{
// Type 3, length 3, data = shift
return TCPOption("window scaling", { 3, 3, shift_count });
}
TCPOption
TCPOption::timeStamp(uint value, uint echo_reply)
{
vector<u_char> data { 8, 0 }; // Type 8, size set below
vec_append(data, htonl(value));
vec_append(data, htonl(echo_reply));
data[1] = data.size();
return TCPOption("timestamp", data);
}
TCPOption
TCPOption::selectiveACK(const vector<pair<uint, uint>> &edges)
{
vector<u_char> data { 5, 0 }; // Type 8, size set below
for (auto const &edge : edges) {
vec_append(data, htonl(edge.first));
vec_append(data, htonl(edge.second));
}
data[1] = data.size();
return TCPOption("sack", data);
}
// Append a TCP option to a u_char vector
void
vec_append(vector<u_char> &target, const TCPOption &source)
{
vec_append(target, source.build());
}
//
// Checksum calculation
// This is NOT an efficient implementation. It's used because it is straight-forward.
// Also, it's not the same as in the streamer, so we test the streamer's algorithm.
//
static uint16_t
bufCSumSimple(const u_char *buff, uint length)
{
uint32_t acc = 0xffff;
// Handle complete 16-bit blocks.
for (size_t i = 0; i+1<length; i += 2) {
uint16_t word;
memcpy(&word, buff + i, 2);
acc += ntohs(word);
if (acc > 0xffff) acc -= 0xffff;
}
// Handle any partial block at the end of the data.
if ((length % 2) == 1) {
uint16_t word = 0;
memcpy(&word, buff + length - 1, 1);
acc += ntohs(word);
if (acc > 0xffff) acc -= 0xffff;
}
return ~static_cast<uint16_t>(acc);
}
// TCP checksum, generic v4/v6 version
static uint16_t
tcpCSum(const vector<u_char> &pseudo_pkt_header, const u_char *tcp, uint total_tcplen)
{
// Finish building the packet after having the pssudo header
auto pseudo_header_size = pseudo_pkt_header.size();
auto pseudo_pkt = pseudo_pkt_header;
vec_append(pseudo_pkt, tcp, total_tcplen);
// Set the pseudo packet's TCP checksum to 0, so it won't be included in the calculation
auto pseudo_tcp = reinterpret_cast<struct TcpHdr *>(&pseudo_pkt[pseudo_header_size]);
pseudo_tcp->check = 0;
return bufCSumSimple(pseudo_pkt.data(), pseudo_pkt.size());
}
// This isn't efficient in the calculaiton of the psuedo header, and is not suitable for general use!
static uint16_t
tcpV4CSum(const struct ip *ip)
{
auto tcp_buf = reinterpret_cast<const u_char *>(ip) + (ip->ip_hl * 4);
uint iplen = ntohs(ip->ip_len);
uint total_tcplen = iplen - (ip->ip_hl * 4);
// Build a psuedo IP header for the calcualtion of the TCP checksum
vector<u_char> pseudo_pkt;
vec_append(pseudo_pkt, &(ip->ip_src), sizeof(ip->ip_src));
vec_append(pseudo_pkt, &(ip->ip_dst), sizeof(ip->ip_dst));
uint16_t ipproto = htons(IPPROTO_TCP);
vec_append(pseudo_pkt, &(ipproto), sizeof(ipproto));
uint16_t len = htons(total_tcplen);
vec_append(pseudo_pkt, &len, sizeof(len));
return tcpCSum(pseudo_pkt, tcp_buf, total_tcplen);
}
// This isn't efficient in the calculaiton of the psuedo header, and is not suitable for general use!
static uint16_t
tcpV6CSum(const struct ip6_hdr *ip6)
{
auto tcp_buf = reinterpret_cast<const u_char *>(ip6) + sizeof(*ip6);
uint total_tcplen = ntohs(ip6->ip6_ctlun.ip6_un1.ip6_un1_plen); // Why so simple?
// Build a psuedo IP header for the calcualtion of the TCP checksum
vector<u_char> pseudo_pkt;
vec_append(pseudo_pkt, &(ip6->ip6_src), sizeof(ip6->ip6_src));
vec_append(pseudo_pkt, &(ip6->ip6_dst), sizeof(ip6->ip6_dst));
uint16_t ipproto = htons(IPPROTO_TCP);
vec_append(pseudo_pkt, &(ipproto), sizeof(ipproto));
uint16_t len = htons(total_tcplen);
vec_append(pseudo_pkt, &(len), sizeof(len));
return tcpCSum(pseudo_pkt, tcp_buf, total_tcplen);
}
uint16_t
ipv4_csum(const struct ip *ip)
{
// Copy the IP header aside
const u_char *ip_p = reinterpret_cast<const u_char *>(ip);
vector<u_char> ip_copy(ip_p, ip_p+(ip->ip_hl * 4));
auto ip_copy_p = reinterpret_cast<struct ip *>(ip_copy.data());
// Set the checksum to 0 so it won't be included in the calculation
ip_copy_p->ip_sum = 0;
// Calculate the checksum
return bufCSumSimple(ip_copy.data(), ip_copy.size());
}
//
// TCP packet generation
// This code can generate various TCP packets, for testing purposes
//
class TCPPacket::Impl
{
public:
Impl(CDir _cdir);
unique_ptr<Packet> build(const ConnKey &ck) const;
uint tcp_seq = 1200; // Arbitrary
uint tcp_ack = 3300000; // Arbitrary
uint16_t tcp_window = 4096; // Reasonable
string tcp_flags = "A"; // Default to simple ACK
bool tcp_cksum_auto = true; // Auto checksum by default
uint16_t tcp_cksum_override;
uint16_t tcp_urgent_ptr = 0;
uint tcp_header_size = sizeof(struct TcpHdr);
int tcp_data_offset = -1;
vector<u_char> l2_header;
vector<u_char> tcp_payload;
vector<TCPOption> tcp_options;
private:
bool has_tcp_flag(char letter) const;
uint l4_hdr_len() const;
// Methods to build the packet data, step by step
vector<u_char> build_pkt_bytes(const ConnKey &key) const;
void emit_l2_hdr (vector<u_char> &pkt) const;
void emit_l3_hdr (vector<u_char> &pkt, const ConnKey &ck) const;
void emit_l4_hdr (vector<u_char> &pkt, const ConnKey &ck) const;
void emit_tcp_options(vector<u_char> &pkt) const;
void emit_payload (vector<u_char> &pkt) const;
void fixup_l4_cksum (vector<u_char> &pkt, IPType type) const;
void fixup_l3_cksum (vector<u_char> &pkt, IPType type) const;
CDir cdir;
};
TCPPacket::Impl::Impl(CDir _cdir)
:
cdir(_cdir)
{
}
uint
TCPPacket::Impl::l4_hdr_len() const
{
// Basic length
uint sz = sizeof(struct TcpHdr);
// TCP options
for (auto const &opt : tcp_options) {
sz += opt.size();
}
// Align to multiple of 4
while (sz%4 != 0) {
sz++;
}
return sz;
}
bool
TCPPacket::Impl::has_tcp_flag(char letter) const
{
return tcp_flags.find(letter) != string::npos;
}
unique_ptr<Packet>
TCPPacket::Impl::build(const ConnKey &ck) const
{
// Figure out the key to use
auto key = ck;
if (cdir == CDir::S2C) key.reverse();
// Build the packet data
auto data = build_pkt_bytes(key);
// Build a Packet, set the conn and cdir
auto pkt_type = l2_header.empty() ? PktType::PKT_L3 : PktType::PKT_L2;
auto p = Packet::genPacket(pkt_type, key.getType(), data);
if (!p.ok()) {
dbgError(D_STREAMING) << "Failed to build packet for " << key << " err=" << (int)p.getErr() <<
" payload: " << Buffer(data);
return nullptr;
}
(*p)->setCDir(cdir);
return p.unpackMove();
}
vector<u_char>
TCPPacket::Impl::build_pkt_bytes(const ConnKey &key) const
{
vector<u_char> data;
emit_l3_hdr(data, key);
emit_l4_hdr(data, key);
emit_payload(data);
fixup_l4_cksum(data, key.getType());
fixup_l3_cksum(data, key.getType());
emit_l2_hdr(data); // Insert l2_hdr at the beginning.
return data;
}
void
TCPPacket::Impl::emit_l2_hdr(vector<u_char> &pkt) const
{
// We use 'insert' and not vec_append because l2 header should be at the beginning.
pkt.insert(pkt.begin(), l2_header.begin(), l2_header.end());
}
void
TCPPacket::Impl::emit_l3_hdr(vector<u_char> &pkt, const ConnKey &ck) const
{
uint payload_length = l4_hdr_len() + tcp_payload.size();
if (ck.getType() == IPType::V4) {
struct ip iphdr = {
ip_hl : 5,
ip_v : 4,
ip_tos : 0,
ip_len : htons(sizeof(struct ip) + payload_length),
ip_id : htons(7766),
ip_off : htons(0x4000), // flags + offset. flags set to don't fragment
ip_ttl : 64,
ip_p : IPPROTO_TCP,
ip_sum : 0, // will be fixed by fixup_l3_cksum
ip_src : ck.getSrc().getIPv4(), // already in network order in the ck
ip_dst : ck.getDst().getIPv4(), // already in network order in the ck
};
vec_append(pkt, &iphdr, sizeof(iphdr));
} else {
struct ip6_hdr ip6hdr;
// The IPv6 header is simple. Linux's headers, however, are like this:
ip6hdr.ip6_ctlun.ip6_un1.ip6_un1_flow = 0;
ip6hdr.ip6_ctlun.ip6_un1.ip6_un1_plen = htons(payload_length);
ip6hdr.ip6_ctlun.ip6_un1.ip6_un1_nxt = IPPROTO_TCP;
ip6hdr.ip6_ctlun.ip6_un1.ip6_un1_hlim = 123;
ip6hdr.ip6_ctlun.ip6_un2_vfc = 0x60; // Overwrites part of ip6_un1_flow
ip6hdr.ip6_src = ck.getSrc().getIPv6();
ip6hdr.ip6_dst = ck.getDst().getIPv6();
vec_append(pkt, &ip6hdr, sizeof(ip6hdr));
}
}
void
TCPPacket::Impl::emit_l4_hdr(vector<u_char> &pkt, const ConnKey &ck) const
{
// Basic header
struct TcpHdr tcp;
tcp.source = htons(ck.getSPort());
tcp.dest = htons(ck.getDPort());
tcp.seq = htonl(tcp_seq);
tcp.ack_seq = htonl(tcp_ack);
tcp.res1 = 0; // unused 4 bits
tcp.doff = static_cast<u_char>(tcp_data_offset > -1 ? tcp_data_offset
: l4_hdr_len() / 4);
tcp.fin = has_tcp_flag('F');
tcp.syn = has_tcp_flag('S');
tcp.rst = has_tcp_flag('R');
tcp.psh = has_tcp_flag('P');
tcp.ack = has_tcp_flag('A');
tcp.urg = has_tcp_flag('U');
tcp.res2 = 0; // ECE and CWR. Never mind them.
tcp.window = htons(tcp_window);
tcp.check = 0; // will be fixed by fixup_l4_cksum
tcp.urg_ptr = htons(tcp_urgent_ptr);
vec_append(pkt, &tcp, tcp_header_size);
// TCP Options
emit_tcp_options(pkt);
}
void
TCPPacket::Impl::emit_tcp_options(vector<u_char> &pkt) const
{
// Concatenate options in a vector, then append with NOPs to multiple of 4
vector<u_char> optbuf;
for (auto const &opt : tcp_options) {
vec_append(optbuf, opt);
}
while (optbuf.size()%4 != 0) {
vec_append(optbuf, TCPOption::NOP);
}
dbgAssert(optbuf.size() <= 40) << "too many tcp options. max is 40 bytes";
vec_append(pkt, optbuf);
}
void
TCPPacket::Impl::emit_payload(vector<u_char> &pkt) const
{
vec_append(pkt, tcp_payload);
}
void
TCPPacket::Impl::fixup_l4_cksum(vector<u_char> &pkt, IPType type) const
{
u_char *l3 = pkt.data();
if (type == IPType::V4) {
if (pkt.size() < sizeof(struct ip) + sizeof(struct TcpHdr)) return;
auto ip = reinterpret_cast<struct ip *>(l3);
auto tcp = reinterpret_cast<struct TcpHdr *>(l3 + (ip->ip_hl*4));
tcp->check = htons(tcp_cksum_auto ? tcpV4CSum(ip) : tcp_cksum_override);
} else {
if (pkt.size() < sizeof(struct ip6_hdr) + sizeof(struct TcpHdr)) return;
auto ip6 = reinterpret_cast<struct ip6_hdr *>(l3);
auto tcp = reinterpret_cast<struct TcpHdr *>(l3 + sizeof(*ip6));
tcp->check = htons(tcp_cksum_auto ? tcpV6CSum(ip6) : tcp_cksum_override);
}
}
void
TCPPacket::Impl::fixup_l3_cksum(vector<u_char> &pkt, IPType type) const
{
if (type == IPType::V4) {
auto ip = reinterpret_cast<struct ip *>(pkt.data());
ip->ip_sum = ipv4_csum(ip);
} else {
// No checksum in IPv6 header. Hurray!
}
}
TCPPacket::TCPPacket(CDir _cdir)
:
pimpl(make_unique<Impl>(_cdir))
{
}
TCPPacket::TCPPacket(TCPPacket &&from)
:
pimpl(std::move(from.pimpl))
{
}
TCPPacket::~TCPPacket()
{
}
TCPPacket &
TCPPacket::setTCPPayload(const vector<u_char> &payload)
{
pimpl->tcp_payload = payload;
return *this;
}
TCPPacket &
TCPPacket::setTCPPayload(const string &payload)
{
vector<u_char> vec;
vec.insert(vec.end(), payload.begin(), payload.end());
return setTCPPayload(vec);
}
TCPPacket &
TCPPacket::addTCPOption(const TCPOption &option)
{
pimpl->tcp_options.push_back(option);
return *this;
}
TCPPacket &
TCPPacket::setL4HeaderSize(uint header_size)
{
pimpl->tcp_header_size = header_size;
return *this;
}
TCPPacket &
TCPPacket::setL4DataOffset(uint data_offset)
{
pimpl->tcp_data_offset = data_offset;
return *this;
}
TCPPacket &
TCPPacket::setTCPSeq(uint _tcp_seq)
{
pimpl->tcp_seq = _tcp_seq;
return *this;
}
TCPPacket &
TCPPacket::setTCPAck(uint _tcp_ack)
{
pimpl->tcp_ack = _tcp_ack;
return *this;
}
TCPPacket &
TCPPacket::setTCPWindow(uint16_t _tcp_window)
{
pimpl->tcp_window = _tcp_window;
return *this;
}
TCPPacket &
TCPPacket::setTCPFlags(string _tcp_flags)
{
pimpl->tcp_flags = _tcp_flags;
return *this;
}
TCPPacket &
TCPPacket::setTCPUrgentPtr(uint16_t _tcp_urgent_ptr)
{
pimpl->tcp_urgent_ptr = _tcp_urgent_ptr;
return *this;
}
TCPPacket &
TCPPacket::setTCPCksum(uint _tcp_cksum_override)
{
pimpl->tcp_cksum_auto = false;
pimpl->tcp_cksum_override = _tcp_cksum_override;
return *this;
}
TCPPacket &
TCPPacket::setL2Header(const vector<u_char> &_l2_header)
{
pimpl->l2_header = _l2_header;
return *this;
}
uint
TCPPacket::getTCPSeq() const
{
return pimpl->tcp_seq;
}
unique_ptr<Packet>
TCPPacket::build(const ConnKey &ck) const
{
return pimpl->build(ck);
}
uint16_t
TCPPacket::calcTCPv4Checksum(const vector<u_char> &pkt)
{
auto l3 = pkt.data();
auto ip = reinterpret_cast<const struct ip *>(l3);
return tcpV4CSum(ip);
}
uint16_t
TCPPacket::calcTCPv6Checksum(const vector<u_char> &pkt)
{
auto l3 = pkt.data();
auto ip6 = reinterpret_cast<const struct ip6_hdr *>(l3);
return tcpV6CSum(ip6);
}
uint16_t
TCPPacket::calcIPv4Checksum(const vector<u_char> &pkt)
{
auto l3 = pkt.data();
auto ip = reinterpret_cast<const struct ip *>(l3);
return ipv4_csum(ip);
}

View File

@@ -0,0 +1,5 @@
add_unit_test(
cptest_ut
"cptest_ut.cc;cptest_packet_ut.cc"
"buffers;packet;connkey;singleton;logging"
)

View File

@@ -0,0 +1,222 @@
#include "cptest/cptest_tcppacket.h"
#include <fstream>
#include "cptest.h"
#include "c_common/network_defs.h"
#include "byteorder.h"
using namespace std;
using namespace testing;
class PacketTest : public Test
{
public:
// Extract TCP options from a packet
static Buffer
getOptions(const Packet *p)
{
auto tcpHdr = p->getL4Header();
tcpHdr.truncateHead(sizeof(struct TcpHdr));
return tcpHdr;
}
ConnKey ck4{IPAddr::createIPAddr("10.0.0.1").unpack(), 1234, IPAddr::createIPAddr("20.0.0.2").unpack(), 80, 6};
ConnKey ck6{IPAddr::createIPAddr("10::1").unpack(), 1234, IPAddr::createIPAddr("20::2").unpack(), 80, 6};
};
TEST_F(PacketTest, base)
{
TCPPacket p(CDir::C2S);
EXPECT_EQ(ck4, p.build(ck4)->getKey());
}
TEST_F(PacketTest, move)
{
TCPPacket p(CDir::C2S);
auto p2 = std::move(p);
EXPECT_EQ(ck4, p2.build(ck4)->getKey());
}
TEST_F(PacketTest, buildConn)
{
auto pkt = TCPPacket(CDir::C2S).build(ck4);
EXPECT_EQ(ck4, pkt->getKey());
}
TEST_F(PacketTest, reverse)
{
TCPPacket p(CDir::S2C);
ConnKey rev = ck6;
rev.reverse();
EXPECT_EQ(rev, p.build(ck6)->getKey());
}
TEST_F(PacketTest, payloadStr)
{
auto pkt = TCPPacket(CDir::C2S)
.setTCPPayload("hello")
.build(ck4);
EXPECT_EQ(Buffer(string("hello")), pkt->getL4Data());
}
TEST_F(PacketTest, payloadVec)
{
auto pkt = TCPPacket(CDir::C2S)
.setTCPPayload(vector<u_char>{'h', 'e', 'l', 'l', 'o'})
.build(ck6);
EXPECT_EQ(Buffer(string("hello")), pkt->getL4Data());
}
TEST_F(PacketTest, TcpParams)
{
auto pkt = TCPPacket(CDir::C2S)
.setTCPSeq(1234)
.setTCPAck(5678)
.setTCPWindow(1000)
.setTCPFlags("SA")
.setTCPUrgentPtr(0)
.setTCPCksum(9999)
.build(ck4);
auto tcp = pkt->getL4Header().getTypePtr<struct TcpHdr>(0).unpack();
EXPECT_EQ(constNTOHL(1234), tcp->seq);
EXPECT_EQ(constNTOHL(5678), tcp->ack_seq);
EXPECT_EQ(constNTOHS(1000), tcp->window);
EXPECT_EQ(TH_SYN|TH_ACK, tcp->flags);
EXPECT_EQ(0, tcp->urg_ptr);
EXPECT_EQ(constNTOHS(9999), tcp->check);
}
TEST_F(PacketTest, getSeq)
{
auto p = TCPPacket(CDir::C2S).setTCPSeq(1234).move();
EXPECT_EQ(1234u, p.getTCPSeq());
}
TEST_F(PacketTest, l2HeaderV4)
{
vector<u_char> mac = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0x08, 0x00 };
auto pkt = TCPPacket(CDir::C2S)
.setL2Header(mac)
.build(ck4);
EXPECT_EQ(Buffer(vector<u_char>(mac)), pkt->getL2Header());
}
TEST_F(PacketTest, l2HeaderV6)
{
vector<u_char> mac = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0x86, 0xdd };
auto pkt = TCPPacket(CDir::C2S)
.setL2Header(mac)
.build(ck6);
EXPECT_EQ(Buffer(vector<u_char>(mac)), pkt->getL2Header());
}
TEST_F(PacketTest, optionsNop)
{
auto pkt = TCPPacket(CDir::C2S)
.addTCPOption(TCPOption::NOP)
.build(ck4);
// 1 NOP, padded with 3 more
EXPECT_EQ(Buffer(vector<u_char>(4, '\x01')), getOptions(pkt.get()));
}
TEST_F(PacketTest, optionsNop6)
{
auto pkt = TCPPacket(CDir::C2S)
.addTCPOption(TCPOption::NOP)
.addTCPOption(TCPOption::NOP)
.addTCPOption(TCPOption::NOP)
.addTCPOption(TCPOption::NOP)
.addTCPOption(TCPOption::NOP)
.addTCPOption(TCPOption::NOP)
.build(ck6);
// 6 NOPs, padded with 2 more
EXPECT_EQ(Buffer(vector<u_char>(8, '\x01')), getOptions(pkt.get()));
}
TEST_F(PacketTest, optionsSACK)
{
auto pkt = TCPPacket(CDir::C2S)
.addTCPOption(TCPOption::SACK_PERMITTED)
.build(ck4);
// SACK_PERMITTED, len=2, 2 NOP padding
EXPECT_EQ(Buffer(vector<u_char>{'\x04', '\x02', '\x01', '\x01'}), getOptions(pkt.get()));
}
TEST_F(PacketTest, optionsWscale)
{
auto pkt = TCPPacket(CDir::C2S)
.addTCPOption(TCPOption::windowScaling(5))
.build(ck6);
// Scaling, len=3, shift=5, 1 NOP padding
EXPECT_EQ(Buffer(vector<u_char>{'\x03', '\x03', '\x05', '\x01'}), getOptions(pkt.get()));
}
TEST_F(PacketTest, optionsTstamp)
{
auto pkt = TCPPacket(CDir::C2S)
.addTCPOption(TCPOption::timeStamp(0x41424344, 0x45464748))
.build(ck4);
// Timestamp, len=10, value=ABCD, echo=EFGH, 2 NOP padding
EXPECT_EQ(Buffer(string("\x08\x0a" "ABCDEFGH" "\x01\x01")), getOptions(pkt.get()));
}
TEST_F(PacketTest, optionsSack)
{
std::vector<std::pair<uint, uint>> edges = { { 0x41424344, 0x45464748 }, { 0x30313233, 0x34353637 } };
auto pkt = TCPPacket(CDir::C2S)
.addTCPOption(TCPOption::selectiveACK(edges))
.build(ck6);
// SACK, len=18, pairs= ABCD, EFGH, 1234, 5678, 2 NOP padding
EXPECT_EQ(Buffer(string("\x05\x12" "ABCDEFGH" "01234567" "\x01\x01")), getOptions(pkt.get()));
}
TEST_F(PacketTest, smallHeader)
{
auto pkt = TCPPacket(CDir::C2S)
.setL4HeaderSize(10) // Too small, will fail
.build(ck4);
EXPECT_EQ(nullptr, pkt);
}
TEST_F(PacketTest, largeDataOffset)
{
auto pkt = TCPPacket(CDir::C2S)
.setL4DataOffset(6) // 6*4 is larger than packet, will fail
.build(ck6);
EXPECT_EQ(nullptr, pkt);
}
TEST_F(PacketTest, cksumV4)
{
// Get ourselves a reasonable IPv4 packet
auto pkt = TCPPacket(CDir::C2S).build(ck4);
auto buf = pkt->getPacket();
auto p = buf.data();
vector<u_char> data(p, p + buf.size());
// XXX: constNTOHS commetned to make it work. Endianity bug?
auto ip = pkt->getL3Header().getTypePtr<struct ip>(0).unpack();
EXPECT_EQ(ip->ip_sum, TCPPacket::calcIPv4Checksum(data));
auto tcp = pkt->getL4Header().getTypePtr<struct TcpHdr>(0).unpack();
EXPECT_EQ(constNTOHS(tcp->check), TCPPacket::calcTCPv4Checksum(data));
}
TEST_F(PacketTest, cksumV6)
{
// Get ourselves a reasonable IPv6 packet
auto pkt = TCPPacket(CDir::C2S).build(ck6);
auto buf = pkt->getPacket();
auto p = buf.data();
vector<u_char> data(p, p + buf.size());
auto tcp = pkt->getL4Header().getTypePtr<struct TcpHdr>(0).unpack();
EXPECT_EQ(constNTOHS(tcp->check), TCPPacket::calcTCPv6Checksum(data));
}

View File

@@ -0,0 +1,67 @@
#include "cptest.h"
#include <fstream>
using namespace std;
using namespace testing;
TEST(CPTest, PrepareToDie)
{
cptestPrepareToDie();
auto die = []() {
dbgAssert(false) << "You killed my father";
};
EXPECT_DEATH(die(), "You killed my father");
}
TEST(Hex, parse)
{
auto v = cptestParseHex("0000: 01 02 03");
EXPECT_THAT(v, ElementsAre(1, 2, 3));
}
TEST(Hex, generate)
{
auto hex = cptestGenerateHex(vector<u_char>{'h', 'e', 'l', 'l', 'o'}, false);
EXPECT_THAT(hex, HasSubstr("68 65 6c 6c 6f")); // hello in hex
}
TEST(Hex, generateWithOffset)
{
auto hex = cptestGenerateHex(vector<u_char>{'h', 'e', 'l', 'l', 'o'}, true);
EXPECT_THAT(hex, StartsWith("0000:"));
EXPECT_THAT(hex, HasSubstr("68 65 6c 6c 6f")); // hello in hex
}
TEST(File, tempEmpty)
{
CPTestTempfile t;
ifstream ifs(t.fname, ifstream::in);
ostringstream os;
os << ifs.rdbuf();
EXPECT_EQ("", os.str());
}
TEST(File, tempNotEmpty)
{
vector<string> lines = {
"hello",
"world"
};
CPTestTempfile t(lines);
ifstream ifs(t.fname, ifstream::in);
ostringstream os;
os << ifs.rdbuf();
EXPECT_EQ("hello\nworld\n", os.str());
}
TEST(File, pathInExeDir)
{
string p = cptestFnameInExeDir("try.txt");
EXPECT_THAT(p, EndsWith("/try.txt"));
}
TEST(File, pathInSrcDir)
{
string p = cptestFnameInSrcDir("try.txt");
EXPECT_THAT(p, EndsWith("/core/cptest/cptest_ut/try.txt"));
}