dnsmeter/src/dns_sender_thread.cpp
Daniel Baumann 36fe29e3d5
Adding upstream version 1.0.2.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-04-23 13:20:07 +02:00

332 lines
9.1 KiB
C++

/*
* Copyright (c) 2019-2021, OARC, Inc.
* Copyright (c) 2019, DENIC eG
* All rights reserved.
*
* This file is part of dnsmeter.
*
* dnsmeter is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* dnsmeter is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with dnsmeter. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "dns_sender_thread.h"
#include "query.h"
#include "exceptions.h"
#include <netinet/in.h>
#include <arpa/inet.h>
#include <math.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <string.h>
#include <errno.h>
DNSSenderThread::DNSSenderThread()
{
buffer = (unsigned char*)malloc(4096);
if (!buffer)
throw ppl7::OutOfMemoryException();
Timeslice = 0.0f;
runtime = 10;
timeout = 5;
queryrate = 0;
counter_packets_send = 0;
counter_bytes_send = 0;
errors = 0;
counter_0bytes = 0;
duration = 0.0;
for (int i = 0; i < 255; i++)
counter_errorcodes[i] = 0;
verbose = false;
spoofingEnabled = false;
DnssecRate = 0;
dnsseccounter = 0;
payload = NULL;
spoofing_net_start = 0;
spoofing_net_size = 0;
payloadIsPcap = false;
spoofingFromPcap = false;
}
DNSSenderThread::~DNSSenderThread()
{
free(buffer);
}
void DNSSenderThread::setDestination(const ppl7::IPAddress& ip, int port)
{
Socket.setDestination(ip, port);
pkt.setDestination(ip, port);
}
void DNSSenderThread::setPayload(PayloadFile& payload)
{
this->payload = &payload;
this->payloadIsPcap = payload.isPcap();
}
void DNSSenderThread::setRuntime(int seconds)
{
runtime = seconds;
}
void DNSSenderThread::setTimeout(int seconds)
{
timeout = seconds;
}
void DNSSenderThread::setDNSSECRate(int rate)
{
DnssecRate = rate;
}
void DNSSenderThread::setQueryRate(ppluint64 qps)
{
queryrate = qps;
}
void DNSSenderThread::setTimeslice(float ms)
{
if (ms == 0.0f || ms > 1000.0f)
throw ppl7::InvalidArgumentsException();
//if ((1000 % ms)!=0) throw ppl7::InvalidArgumentsException();
Timeslice = (double)ms / 1000;
}
void DNSSenderThread::setSourceIP(const ppl7::IPAddress& ip)
{
sourceip = ip;
spoofingEnabled = false;
}
void DNSSenderThread::setSourceNet(const ppl7::IPNetwork& net)
{
sourcenet = net;
spoofingEnabled = true;
spoofing_net_start = ntohl(*(in_addr_t*)net.first().addr());
spoofing_net_size = powl(2, 32 - net.prefixlen());
}
void DNSSenderThread::setSourcePcap()
{
spoofingEnabled = true;
spoofingFromPcap = true;
}
void DNSSenderThread::setVerbose(bool verbose)
{
this->verbose = verbose;
}
#define PCAP_HEADER_SIZE 14 + sizeof(struct ip) + sizeof(struct udphdr)
void DNSSenderThread::sendPacket()
{
size_t query_size;
while (1) {
try {
const ppl7::ByteArrayPtr& bap = payload->getQuery();
query_size = bap.size();
if (payloadIsPcap) {
query_size -= PCAP_HEADER_SIZE;
memcpy(buffer, ((const char*)bap.ptr()) + PCAP_HEADER_SIZE, query_size);
} else {
memcpy(buffer, bap.ptr(), query_size);
dnsseccounter += DnssecRate;
if (dnsseccounter >= 100) {
query_size = AddDnssecToQuery(buffer, 4096, query_size);
dnsseccounter -= 100;
}
}
pkt.setPayload(buffer, query_size);
if (spoofingEnabled) {
if (spoofingFromPcap) {
pkt.useSourceFromPcap((const char*)bap.ptr(), bap.size());
} else {
pkt.randomSourceIP(spoofing_net_start, spoofing_net_size);
pkt.randomSourcePort();
}
} else {
pkt.randomSourcePort();
}
pkt.setDnsId(getQueryTimestamp());
ssize_t n = Socket.send(pkt);
if (n > 0 && (size_t)n == pkt.size()) {
counter_packets_send++;
counter_bytes_send += pkt.size();
} else if (n < 0) {
if (errno < 255)
counter_errorcodes[errno]++;
errors++;
} else {
counter_0bytes++;
}
return;
} catch (const UnknownRRType& exp) {
continue;
} catch (const InvalidDNSQuery& exp) {
continue;
}
}
}
void DNSSenderThread::run()
{
if (!payload)
throw ppl7::NullPointerException("payload not set!");
if (!spoofingEnabled) {
pkt.setSource(sourceip, 0x4567);
}
dnsseccounter = 0;
counter_packets_send = 0;
counter_bytes_send = 0;
counter_0bytes = 0;
errors = 0;
duration = 0.0;
for (int i = 0; i < 255; i++)
counter_errorcodes[i] = 0;
double start = ppl7::GetMicrotime();
if (queryrate > 0) {
runWithRateLimit();
} else {
runWithoutRateLimit();
}
duration = ppl7::GetMicrotime() - start;
waitForTimeout();
}
void DNSSenderThread::runWithoutRateLimit()
{
double start = ppl7::GetMicrotime();
double end = start + (double)runtime;
double now;
int pc = 0;
while (1) {
sendPacket();
pc++;
if (pc > 10000) {
pc = 0;
if (this->threadShouldStop())
break;
now = ppl7::GetMicrotime();
if (now > end)
break;
}
}
}
static inline double getNsec()
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return (double)ts.tv_sec + ((double)ts.tv_nsec / 1000000000.0);
}
void DNSSenderThread::runWithRateLimit()
{
struct timespec ts;
ppluint64 total_timeslices = runtime * 1000 / (Timeslice * 1000.0);
ppluint64 queries_rest = runtime * queryrate;
ppl7::SockAddr addr = Socket.getSockAddr();
verbose = true;
if (verbose) {
//printf ("qps=%d, runtime=%d\n",queryrate, runtime);
printf("runtime: %d s, timeslice: %0.6f s, total timeslices: %llu, Qpts: %llu, Source: %s:%d\n",
runtime, Timeslice, total_timeslices,
queries_rest / total_timeslices,
(const char*)addr.toIPAddress().toString(), addr.port());
}
double now = getNsec();
double next_timeslice = now;
double next_checktime = now + 0.1;
double start = ppl7::GetMicrotime();
double end = start + (double)runtime;
double total_idle = 0.0;
for (ppluint64 z = 0; z < total_timeslices; z++) {
next_timeslice += Timeslice;
ppluint64 timeslices_rest = total_timeslices - z;
ppluint64 queries_per_timeslice = queries_rest / timeslices_rest;
if (timeslices_rest == 1)
queries_per_timeslice = queries_rest;
for (ppluint64 i = 0; i < queries_per_timeslice; i++) {
sendPacket();
}
queries_rest -= queries_per_timeslice;
while ((now = getNsec()) < next_timeslice) {
if (now < next_timeslice) {
total_idle += next_timeslice - now;
ts.tv_sec = 0;
ts.tv_nsec = (next_timeslice - now) * 1000000000;
nanosleep(&ts, NULL);
}
}
if (now > next_checktime) {
next_checktime = now + 0.1;
if (this->threadShouldStop())
break;
if (ppl7::GetMicrotime() >= end)
break;
//printf ("Zeitscheiben rest: %llu\n", z);
}
}
if (verbose) {
//printf ("total idle: %0.6f\n",total_idle);
}
}
void DNSSenderThread::waitForTimeout()
{
double start = ppl7::GetMicrotime();
double end = start + (double)timeout;
double now, next_checktime = start + 0.1;
while ((now = ppl7::GetMicrotime()) < end) {
if (now > next_checktime) {
next_checktime = now + 0.1;
if (this->threadShouldStop())
break;
}
ppl7::MSleep(10);
}
}
ppluint64 DNSSenderThread::getPacketsSend() const
{
return counter_packets_send;
}
ppluint64 DNSSenderThread::getBytesSend() const
{
return counter_bytes_send;
}
ppluint64 DNSSenderThread::getErrors() const
{
return errors;
}
ppluint64 DNSSenderThread::getCounter0Bytes() const
{
return counter_0bytes;
}
ppluint64 DNSSenderThread::getCounterErrorCode(int err) const
{
if (err < 255)
return counter_errorcodes[err];
return 0;
}