mirror of
https://github.com/openappsec/openappsec.git
synced 2025-06-28 16:41:02 +03:00
594 lines
15 KiB
C++
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);
|
|
}
|