/*
* Copyright (c) 2017-2024 OARC, Inc.
* Copyright (c) 2011-2017, IIS - The Internet Foundation in Sweden
* All rights reserved.
*
* This file is part of PacketQ.
*
* PacketQ 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.
*
* PacketQ 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 PacketQ. If not, see .
*/
#ifndef __packetq_dns_h
#define __packetq_dns_h
#include
#include
#include
#include
#include "output.h"
#include "packet_handler.h"
#include "tcp.h"
namespace packetq {
extern char visible_char_map[256];
class DNSMessage {
public:
class Header {
public:
int id;
int z;
bool qr;
int opcode;
bool aa;
bool tc;
bool rd;
bool ra;
bool ad;
bool cd;
int rcode;
int qdcount;
int ancount;
int nscount;
int arcount;
Header()
: z(0)
{
id = 0;
qr = 0;
opcode = 0;
aa = 0;
tc = 0;
rd = 0;
ra = 0;
ad = 0;
cd = 0;
rcode = 0;
qdcount = 0;
ancount = 0;
nscount = 0;
arcount = 0;
}
void parse(DNSMessage& p)
{
/*
From rfc 2929
1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ID |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR| Opcode |AA|TC|RD|RA| Z|AD|CD| RCODE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QDCOUNT/ZOCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ANCOUNT/PRCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| NSCOUNT/UPCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ARCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
id = p.get_ushort(0);
qr = p.get_bit(2, 0);
opcode = p.get_bits(2, 1, 4);
aa = p.get_bit(2, 5);
tc = p.get_bit(2, 6);
rd = p.get_bit(2, 7);
ra = p.get_bit(2, 8);
ad = p.get_bit(2, 10);
cd = p.get_bit(2, 11);
rcode = p.get_bits(2, 12, 4);
qdcount = p.get_ushort(4);
ancount = p.get_ushort(6);
nscount = p.get_ushort(8);
arcount = p.get_ushort(10);
}
};
class Name {
public:
char fqdn[2048]; // escaping needs *4 the space
int labels;
Name()
: fqdn { 0 }
, labels(0)
{
}
void reset(void)
{
fqdn[0] = 0;
labels = 0;
}
};
class Question {
public:
Name qname;
int qtype;
int qclass;
Question()
{
qtype = 0;
qclass = 0;
}
int parse(DNSMessage& m, int offs)
{
offs = m.parse_dname(qname, offs);
qtype = m.get_ushort(offs);
offs += 2;
qclass = m.get_ushort(offs);
offs += 2;
return offs;
}
};
class RR {
public:
Name name;
int type;
int rr_class;
unsigned int ttl;
int rdlength;
int doffs;
RR()
{
type = 0;
rr_class = 0;
ttl = 0;
rdlength = 0;
doffs = 0;
}
int parse(DNSMessage& m, int offs)
{
offs = m.parse_dname(name, offs);
type = m.get_ushort(offs);
if (type == 41) {
m.m_opt_rr = this;
m.m_new_opt_rr = true;
}
offs += 2;
rr_class = m.get_ushort(offs);
offs += 2;
ttl = m.get_ushort(offs) << 16;
ttl |= m.get_ushort(offs + 2);
offs += 4;
rdlength = m.get_ushort(offs);
offs += 2;
doffs = offs;
offs += rdlength;
return offs;
}
};
IP_header& m_ip_header;
unsigned char* m_data;
int m_length;
Header m_header;
Question m_questions[2];
RR m_answer[2];
RR m_authority[2];
RR m_additional[2];
RR* m_opt_rr;
bool m_new_opt_rr;
int m_error;
bool m_edns0;
bool m_do;
int m_extended_rcode;
int m_edns_version;
int m_z;
int m_udp_size;
bool m_edns0_ecs;
int m_edns0_ecs_family;
int m_edns0_ecs_source;
int m_edns0_ecs_scope;
in6addr_t m_edns0_ecs_addr;
bool m_edns0_ecs_addr_set;
DNSMessage(unsigned char* data, int len, IP_header& head)
: m_ip_header(head)
{
m_opt_rr = 0;
m_new_opt_rr = false;
m_error = 0;
m_data = data;
m_length = len;
m_edns0 = false;
m_do = false;
m_extended_rcode = 0;
m_edns_version = 0;
m_z = 0;
m_udp_size = 0;
m_edns0_ecs = false;
m_edns0_ecs_family = 0;
m_edns0_ecs_source = 0;
m_edns0_ecs_scope = 0;
m_edns0_ecs_addr_set = false;
parse();
}
int parse_dname(Name& name, int offs)
{
int p = 0;
int savedoffs = 0;
int n = get_ubyte(offs++);
char* out = &name.fqdn[0];
if (n == 0)
out[p++] = '.';
while (n > 0) {
name.labels++;
while (n >= 192) {
if (savedoffs) {
out[p++] = 0;
return savedoffs;
}
savedoffs = offs + 1;
int n2 = get_ubyte(offs++);
int ptr = (n & 63) * 0x100 + n2;
offs = ptr;
n = get_ubyte(offs++);
}
// if the string is too long restart and mess it up
// check if we can fit a fully escaped label + . and reserve for zeroing it later
if (p + (n * 4) + 1 > sizeof(name.fqdn) - 1)
p = 0;
while (n-- > 0) {
const unsigned int byte = get_ubyte(offs++);
if (visible_char_map[byte]) {
out[p++] = visible_char_map[byte];
} else {
out[p++] = '\\';
out[p++] = '0' + byte / 100;
out[p++] = '0' + byte / 10 % 10;
out[p++] = '0' + byte % 10;
}
}
out[p++] = '.';
n = get_ubyte(offs++);
}
if (savedoffs)
offs = savedoffs;
out[p++] = 0;
return offs;
}
void parse_opt_rr()
{
if (m_opt_rr) {
if (!m_edns0) {
m_edns0 = true;
unsigned long ttl = m_opt_rr->ttl;
m_do = (ttl >> 15) & 1;
m_extended_rcode = ttl >> 24;
m_edns_version = (ttl >> 16) & 0xff;
m_z = ttl & 0x7fff;
m_udp_size = m_opt_rr->rr_class;
}
if (((m_opt_rr->ttl >> 16) & 0xff) == 0 && m_opt_rr->rdlength > 0) {
// Parse this OPT RR that is EDNS0
int rdlen = m_opt_rr->rdlength,
offs = m_opt_rr->doffs,
opcode = 0,
oplen = 0;
while (rdlen > 3) {
// Minimum op code and length
opcode = get_ushort(offs);
oplen = get_ushort(offs + 2);
offs += 4;
rdlen -= 4;
if (rdlen < oplen)
break;
if (opcode == 8 && !m_edns0_ecs && oplen > 3) {
// ECS - Client Subnet - RFC7871
m_edns0_ecs = true;
m_edns0_ecs_family = get_ushort(offs);
m_edns0_ecs_source = get_ubyte(offs + 2);
m_edns0_ecs_scope = get_ubyte(offs + 3);
int addrlen = (m_edns0_ecs_source / 8) + (m_edns0_ecs_source % 8 ? 1 : 0);
int fill = 0;
if (addrlen <= (oplen - 4)) {
switch (m_edns0_ecs_family) {
case 1:
fill = 4;
m_edns0_ecs_addr_set = true;
break;
case 2:
fill = 16;
m_edns0_ecs_addr_set = true;
break;
}
int a = 0, b;
for (b = 15; fill && b > -1; fill--, b--) {
if (a < addrlen) {
m_edns0_ecs_addr.__in6_u.__u6_addr8[b] = get_ubyte(offs + 4 + a);
a++;
} else {
m_edns0_ecs_addr.__in6_u.__u6_addr8[b] = 0;
}
}
}
}
rdlen -= oplen;
offs += oplen;
}
}
}
}
void parse()
{
m_header.parse(*this);
int offs = 12;
int q = 0;
int cnt = m_header.qdcount;
while (cnt-- > 0) {
offs = m_questions[q].parse(*this, offs);
if (offs > m_length) {
m_questions[q].qname.reset();
m_error = offs;
return;
}
q = 1; // not ++ ignore further Q's
}
q = 0;
cnt = m_header.ancount;
while (cnt-- > 0) {
offs = m_answer[q].parse(*this, offs);
q = 1; // not ++ ignore further Q's
if (offs > m_length) {
m_error = offs;
return;
}
}
q = 0;
cnt = m_header.nscount;
while (cnt-- > 0) {
offs = m_authority[q].parse(*this, offs);
q = 1; // not ++ ignore further Q's
if (offs > m_length) {
m_error = offs;
return;
}
}
q = 0;
cnt = m_header.arcount;
while (cnt-- > 0) {
offs = m_additional[q].parse(*this, offs);
q = 1; // not ++ ignore further Q's
if (offs > m_length) {
m_error = offs;
return;
}
if (m_new_opt_rr) {
parse_opt_rr();
m_new_opt_rr = false;
}
}
if (offs > m_length)
m_error = offs;
}
unsigned int get_ubyte(int offs)
{
if (offs >= m_length)
return 0;
return int(m_data[offs]);
}
// returns 16 bit number at byte offset offs
unsigned int get_ushort(int offs)
{
if ((offs + 1) >= m_length)
return 0;
return (int(m_data[offs]) << 8) | int(m_data[offs + 1]);
}
uint32_t get_uint32(int offs)
{
if ((offs + 3) >= m_length)
return 0;
return (uint32_t(m_data[offs]) << 24) | (uint32_t(m_data[offs + 1]) << 16) | (uint32_t(m_data[offs + 2]) << 8) | uint32_t(m_data[offs + 3]);
}
bool get_bit(int offs, int bit)
{
if (offs >= m_length)
return 0;
return ((get_ushort(offs) << bit) & 0x8000) == 0x8000;
}
unsigned int get_bits(int offs, int bit, int bits)
{
if (offs >= m_length)
return 0;
return ((get_ushort(offs) << bit) & 0xffff) >> (16 - bits);
}
};
class Parse_dns : public Packet_handler {
public:
enum {
COLUMN_QNAME = IP_header_to_table::COLUMN_FRAGMENTS + 1,
COLUMN_ANAME,
COLUMN_MSG_ID,
COLUMN_MSG_SIZE,
COLUMN_OPCODE,
COLUMN_RCODE,
COLUMN_EXTENDED_RCODE,
COLUMN_EDNS_VERSION,
COLUMN_Z,
COLUMN_UDP_SIZE,
COLUMN_QD_COUNT,
COLUMN_AN_COUNT,
COLUMN_NS_COUNT,
COLUMN_AR_COUNT,
COLUMN_QTYPE,
COLUMN_QCLASS,
COLUMN_QLABELS,
COLUMN_ATYPE,
COLUMN_ACLASS,
COLUMN_ALABELS,
COLUMN_ATTL,
COLUMN_AA,
COLUMN_TC,
COLUMN_RD,
COLUMN_CD,
COLUMN_RA,
COLUMN_AD,
COLUMN_DO,
COLUMN_EDNS0,
COLUMN_QR,
COLUMN_EDNS0_ECS,
COLUMN_EDNS0_ECS_FAMILY,
COLUMN_EDNS0_ECS_SOURCE,
COLUMN_EDNS0_ECS_SCOPE,
COLUMN_EDNS0_ECS_ADDRESS,
};
Parse_dns(bool escape_dnsnames);
virtual void on_table_created(Table* table, const std::vector& columns);
virtual Packet::ParseResult parse(Packet& packet, const std::vector& columns, Row& destination_row, bool sample);
void add_packet_columns();
void add_lookup_tables();
private:
Str_conv converter;
IP_header_to_table m_ip_helper;
Int_accessor acc_s;
Int_accessor acc_us;
Int_accessor acc_ether_type;
Int_accessor acc_protocol;
Int_accessor acc_src_port;
Int_accessor acc_msg_id;
Int_accessor acc_msg_size;
Int_accessor acc_opcode;
Int_accessor acc_rcode;
Int_accessor acc_extended_rcode;
Int_accessor acc_edns_version;
Int_accessor acc_z;
Int_accessor acc_udp_size;
Int_accessor acc_qd_count;
Int_accessor acc_an_count;
Int_accessor acc_ns_count;
Int_accessor acc_ar_count;
Int_accessor acc_qtype;
Int_accessor acc_qclass;
Int_accessor acc_qlabels;
Int_accessor acc_atype;
Int_accessor acc_aclass;
Int_accessor acc_attl;
Int_accessor acc_alabels;
Int_accessor acc_edns0_ecs_family;
Int_accessor acc_edns0_ecs_source;
Int_accessor acc_edns0_ecs_scope;
Bool_accessor acc_qr;
Bool_accessor acc_aa;
Bool_accessor acc_tc;
Bool_accessor acc_rd;
Bool_accessor acc_cd;
Bool_accessor acc_ra;
Bool_accessor acc_ad;
Bool_accessor acc_do;
Bool_accessor acc_edns0;
Bool_accessor acc_edns0_ecs;
Text_accessor acc_qname;
Text_accessor acc_aname;
Text_accessor acc_src_addr;
Text_accessor acc_dst_addr;
Text_accessor acc_edns0_ecs_address;
};
} // namespace packetq
#endif // __packetq_dns_h