mirror of
https://github.com/openappsec/openappsec.git
synced 2025-09-29 19:24:26 +03:00
First release of open-appsec source code
This commit is contained in:
10
core/cptest/CMakeLists.txt
Normal file
10
core/cptest/CMakeLists.txt
Normal 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
145
core/cptest/cptest.cc
Executable 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
48
core/cptest/cptest_data_buf.cc
Executable 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
593
core/cptest/cptest_tcppacket.cc
Executable 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);
|
||||
}
|
5
core/cptest/cptest_ut/CMakeLists.txt
Normal file
5
core/cptest/cptest_ut/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
add_unit_test(
|
||||
cptest_ut
|
||||
"cptest_ut.cc;cptest_packet_ut.cc"
|
||||
"buffers;packet;connkey;singleton;logging"
|
||||
)
|
222
core/cptest/cptest_ut/cptest_packet_ut.cc
Executable file
222
core/cptest/cptest_ut/cptest_packet_ut.cc
Executable 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));
|
||||
}
|
67
core/cptest/cptest_ut/cptest_ut.cc
Normal file
67
core/cptest/cptest_ut/cptest_ut.cc
Normal 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"));
|
||||
}
|
Reference in New Issue
Block a user