openappsec/core/cptest/cptest_tcppacket.cc
2024-09-17 10:53:09 +00:00

594 lines
15 KiB
C++

#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) << AlertInfo(AlertTeam::CORE, "testing") << "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);
}