First commit
This commit is contained in:
commit
400356e2ce
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
src/**.swp
|
||||
**.log
|
16
Makefile
Normal file
16
Makefile
Normal file
@ -0,0 +1,16 @@
|
||||
GCC=gcc
|
||||
CL=clang-11
|
||||
CFLAGS=-Wall
|
||||
LIBS=-L../libbpf/src -l:libbpf.a -lelf -lz
|
||||
#LIBS=-lbpf
|
||||
|
||||
all: dns-trace.ebpf.o dns-trace
|
||||
|
||||
dns-trace.ebpf.o: src/dns-trace.ebpf.c
|
||||
$(CL) -Wall -g -O2 -target bpf -D __TARGET_ARCH_x86_64 -D __BPF_TRACING__ -c src/dns-trace.ebpf.c -o src/dns-trace.ebpf.o
|
||||
|
||||
dns-trace: src/dns-trace.c
|
||||
$(GCC) $(CFLAGS) src/dns-trace.c -o dns-trace $(LIBS)
|
||||
|
||||
clean:
|
||||
rm -rf src/*.o && rm dns-trace
|
18
README.md
Normal file
18
README.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Introduction
|
||||
|
||||
## Requirements
|
||||
First, you need to install these packages:
|
||||
|
||||
```
|
||||
apt-get install bpftool clang libbpf-dev gcc-multilib
|
||||
```
|
||||
|
||||
Clone the project and compile it:
|
||||
```
|
||||
$ git clone https://github.com/libbpf/libbpf/
|
||||
cd libbpf/src && make
|
||||
```
|
||||
|
||||
After that, you can execute the program:
|
||||
|
||||
|
BIN
dns-trace
Executable file
BIN
dns-trace
Executable file
Binary file not shown.
6
exec.sh
Executable file
6
exec.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/sh
|
||||
|
||||
|
||||
#sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c > src/vmlinux.h
|
||||
make clean
|
||||
make all && sudo ./dns-trace -i wlp0s20f3 -f dns-trace_`$(echo date '+%F')`.log
|
35
src/common.h
Normal file
35
src/common.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef H_COMMON
|
||||
#define H_COMMON
|
||||
|
||||
#define QNAME_SIZE 128
|
||||
|
||||
#define REQ_QUERY 0x00
|
||||
#define REQ_ANSWER 0x01
|
||||
|
||||
/* See section 2.3.4 RFC 1035 */
|
||||
#define MAX_UDP_PAYLOAD 512
|
||||
#define MAx_NAME_LEN 255
|
||||
|
||||
struct dnshdr {
|
||||
uint16_t transactionID;
|
||||
uint16_t flags;
|
||||
uint16_t nbQuestions;
|
||||
uint16_t nbAnswerRRs;
|
||||
uint16_t nbAuthorityRRs;
|
||||
uint16_t nbAdditionalRRs;
|
||||
};
|
||||
|
||||
struct event {
|
||||
uint32_t client;
|
||||
int dport;
|
||||
int sport;
|
||||
uint16_t tid;
|
||||
int req_type;
|
||||
char qname[QNAME_SIZE];
|
||||
uint16_t class;
|
||||
uint16_t type;
|
||||
uint16_t numAns;
|
||||
unsigned char buf[MAX_UDP_PAYLOAD]; // On stocke la data au format size + data
|
||||
};
|
||||
|
||||
#endif
|
638
src/dns-trace.c
Normal file
638
src/dns-trace.c
Normal file
@ -0,0 +1,638 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf.h>
|
||||
#include <bpf/libbpf.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <argp.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <fcntl.h>
|
||||
#include <net/if.h> /* if_nametoindex */
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/if_packet.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
/* Global variables */
|
||||
static struct arguments arguments;
|
||||
static int running = 1;
|
||||
static FILE *f;
|
||||
static char hostname[127];
|
||||
|
||||
|
||||
struct arguments {
|
||||
char *interface;
|
||||
char *filename;
|
||||
int to_output;
|
||||
};
|
||||
|
||||
/*
|
||||
* Functions for arguments
|
||||
* https://www.gnu.org/software/libc/manual/html_node/Argp-Example-3.html
|
||||
*/
|
||||
|
||||
static char doc[] = "DNS Trace usage:";
|
||||
static char args_doc[] = "[ARGS]";
|
||||
|
||||
static error_t parse_opts(int key, char *arg, struct argp_state *state){
|
||||
struct arguments *arguments = state->input;
|
||||
switch(key){
|
||||
case 'f':
|
||||
arguments->filename = arg;
|
||||
arguments->to_output = 0;
|
||||
break;
|
||||
case 'i':
|
||||
arguments->interface = arg;
|
||||
break;
|
||||
case 'o':
|
||||
arguments->to_output = 1;
|
||||
break;
|
||||
case ARGP_KEY_ARG:
|
||||
break;
|
||||
case ARGP_KEY_END:
|
||||
break;
|
||||
default:
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct arguments parse_args(int argc, char *argv[]){
|
||||
static const struct argp_option opts[] = {
|
||||
{"interface", 'i', "INTERFACE", 0, "Ifname for listening"},
|
||||
{"filename", 'f', "FILENAME", 0, "Save result to logs"},
|
||||
{"to-output", 'o', NULL, 0, "Print to output"},
|
||||
{NULL, 'h', NULL, OPTION_HIDDEN, "help"},
|
||||
{},
|
||||
};
|
||||
struct arguments arguments;
|
||||
arguments.interface = NULL;
|
||||
arguments.filename = NULL;
|
||||
arguments.to_output = 1;
|
||||
static struct argp argp = {opts, parse_opts, args_doc, doc};
|
||||
|
||||
argp_parse(&argp, argc, argv, 0, 0, &arguments);
|
||||
return arguments;
|
||||
}
|
||||
|
||||
/* End functions arguments */
|
||||
|
||||
static void signalHandler(int signum){
|
||||
running = 0;
|
||||
}
|
||||
/*
|
||||
* This function create a raw socket and bind it to the ifname
|
||||
*/
|
||||
static int create_rsock(const char *name) {
|
||||
struct sockaddr_ll sll;
|
||||
int sock;
|
||||
|
||||
sock = socket(PF_PACKET, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, htons(ETH_P_ALL));
|
||||
if (sock < 0) {
|
||||
printf("cannot create raw socket\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&sll, 0, sizeof(sll));
|
||||
sll.sll_family = AF_PACKET;
|
||||
sll.sll_ifindex = if_nametoindex(name);
|
||||
sll.sll_protocol = htons(ETH_P_ALL);
|
||||
if (bind(sock, (struct sockaddr *)&sll, sizeof(sll)) < 0) {
|
||||
printf("Failed to bind the interface %s\n", name);
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function map the type of DNS request
|
||||
*/
|
||||
static char *mapReqType(const int req){
|
||||
char *tmp = malloc(8);
|
||||
if (tmp == NULL)
|
||||
return NULL;
|
||||
|
||||
switch(req){
|
||||
case 0x00:
|
||||
strncpy(tmp, "Query", 6);
|
||||
break;
|
||||
case 0x01:
|
||||
strncpy(tmp, "Answer", 7);
|
||||
break;
|
||||
default:
|
||||
strncpy(tmp, "Unknown", 8);
|
||||
};
|
||||
return tmp;
|
||||
}
|
||||
/*
|
||||
* This function map the DNS class RR
|
||||
*/
|
||||
static char *mapClass(const int class){
|
||||
char *tmp = malloc(8);
|
||||
if (tmp == NULL)
|
||||
return NULL;
|
||||
|
||||
memset(tmp, 0, 8);
|
||||
switch(class){
|
||||
case 1:
|
||||
strncpy(tmp, "IN", 3);
|
||||
break;
|
||||
case 2:
|
||||
strncpy(tmp, "CS", 3);
|
||||
break;
|
||||
case 3:
|
||||
strncpy(tmp, "CH", 3);
|
||||
break;
|
||||
case 4:
|
||||
strncpy(tmp, "HS", 3);
|
||||
break;
|
||||
default:
|
||||
strncpy(tmp, "Unknown", 8);
|
||||
break;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
/*
|
||||
* This function map the DNS type RR
|
||||
*/
|
||||
static char *mapType(const int type){
|
||||
char *tmp = malloc(8);
|
||||
if (tmp == NULL)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* type DNS defined in RFC:
|
||||
* https://datatracker.ietf.org/doc/html/rfc1035#section-3.2.2
|
||||
* https://datatracker.ietf.org/doc/html/rfc3596
|
||||
*/
|
||||
switch(type){
|
||||
case 1:
|
||||
strncpy(tmp, "A", 2);
|
||||
break;
|
||||
case 2:
|
||||
strncpy(tmp, "NS", 3);
|
||||
break;
|
||||
case 3:
|
||||
strncpy(tmp, "MD", 3);
|
||||
break;
|
||||
case 4:
|
||||
strncpy(tmp, "MF", 3);
|
||||
break;
|
||||
case 5:
|
||||
strncpy(tmp, "CNAME", 6);
|
||||
break;
|
||||
case 6:
|
||||
strncpy(tmp, "SOA", 4);
|
||||
break;
|
||||
case 7:
|
||||
strncpy(tmp, "MB", 3);
|
||||
break;
|
||||
case 8:
|
||||
strncpy(tmp, "MG", 3);
|
||||
break;
|
||||
case 9:
|
||||
strncpy(tmp, "MR", 3);
|
||||
break;
|
||||
case 10:
|
||||
strncpy(tmp, "NULL", 5);
|
||||
break;
|
||||
case 11:
|
||||
strncpy(tmp, "WKS", 4);
|
||||
break;
|
||||
case 12:
|
||||
strncpy(tmp, "PTR", 4);
|
||||
break;
|
||||
case 13:
|
||||
strncpy(tmp, "HINFO", 6);
|
||||
break;
|
||||
case 14:
|
||||
strncpy(tmp, "MINFO", 6);
|
||||
break;
|
||||
case 15:
|
||||
strncpy(tmp, "MX", 3);
|
||||
break;
|
||||
case 16:
|
||||
strncpy(tmp, "TXT", 4);
|
||||
break;
|
||||
case 28:
|
||||
strncpy(tmp, "AAAA", 5);
|
||||
break;
|
||||
default:
|
||||
strncpy(tmp, "Unknown", 8);
|
||||
break;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function get the localtime into the rsyslog format
|
||||
*/
|
||||
static int syslog_time(time_t ts, char t[32], size_t l){
|
||||
const char format[] = "%b %d %T";
|
||||
struct tm *lt = localtime(&ts);
|
||||
if(strftime(t, l, format, lt) == 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* This function get the hostname of the system
|
||||
* If not find, the hostname is ubuntu
|
||||
*/
|
||||
static void get_hostname(){
|
||||
/* Get the hostname */
|
||||
if (gethostname(hostname, 127) == -1){
|
||||
printf("Failed to get the hostname\n");
|
||||
strncpy(hostname, "ubuntu", 7);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* This function print to the stdout the query section
|
||||
*/
|
||||
static void print_query(struct event *s_event){
|
||||
char *req_type, *class, *type;
|
||||
char t[32];
|
||||
time_t ts = time(NULL);
|
||||
syslog_time(ts, t, sizeof(t));
|
||||
printf("%-20s", t);
|
||||
|
||||
req_type = mapReqType(s_event->req_type);
|
||||
printf("%s ", req_type);
|
||||
free(req_type);
|
||||
|
||||
printf("%5s:%d\t", inet_ntoa(*(struct in_addr*)&s_event->client), s_event->dport);
|
||||
printf("%x\t", s_event->tid);
|
||||
|
||||
class = mapClass(s_event->class);
|
||||
printf("%-5s", class);
|
||||
free(class);
|
||||
|
||||
type = mapType(s_event->type);
|
||||
printf("%-5s", type);
|
||||
free(type);
|
||||
|
||||
printf("%s", s_event->qname);
|
||||
printf("\n");
|
||||
}
|
||||
/*
|
||||
* This function save to rsyslog file the common information
|
||||
*/
|
||||
static void header_to_log(struct event *s_event){
|
||||
char t[32];
|
||||
char tid[12];
|
||||
char src[40];
|
||||
char *req_type;
|
||||
time_t ts = time(NULL);
|
||||
|
||||
if (syslog_time(ts, t, sizeof(t)) == 0)
|
||||
fwrite(t, strlen(t), 1, f);
|
||||
|
||||
fwrite(" ", 1, 1, f);
|
||||
fwrite(hostname, strlen(hostname), 1, f);
|
||||
|
||||
fwrite(" dns-trace: ", 12, 1, f);
|
||||
fwrite("<info> ", 7, 1, f);
|
||||
|
||||
req_type = mapReqType(s_event->req_type);
|
||||
fwrite(req_type, strlen(req_type), 1, f);
|
||||
free(req_type);
|
||||
fwrite(";", 1, 1, f);
|
||||
|
||||
snprintf(tid, 12, "tid=%x;", s_event->tid);
|
||||
fwrite(tid, strlen(tid), 1, f);
|
||||
|
||||
snprintf(src, 40, "%s:%d;", inet_ntoa(*(struct in_addr*)&s_event->client), s_event->dport);
|
||||
fwrite(src, strlen(src), 1, f);
|
||||
}
|
||||
/*
|
||||
* This function save to log file the query section in rsylog format
|
||||
* <time> <hostname> <procname>: <info> <data>
|
||||
*/
|
||||
static void query_to_log(struct event *s_event){
|
||||
char *class, *type;
|
||||
char s_class[16], s_type[16];
|
||||
|
||||
header_to_log(s_event);
|
||||
|
||||
|
||||
class = mapClass(s_event->class);
|
||||
snprintf(s_class, 16, "class=%s;", class);
|
||||
fwrite(s_class, strlen(s_class), 1, f);
|
||||
free(class);
|
||||
|
||||
type = mapType(s_event->type);
|
||||
snprintf(s_type, 16, "type=%s;", type);
|
||||
fwrite(s_type, strlen(s_type), 1, f);
|
||||
free(type);
|
||||
|
||||
fwrite("domain=", 7, 1, f);
|
||||
fwrite(s_event->qname, strlen(s_event->qname), 1, f);
|
||||
|
||||
fwrite(";\n", 2, 1, f);
|
||||
}
|
||||
/*
|
||||
* This function get labels from DNS answer
|
||||
*/
|
||||
static void get_labels(unsigned char *buf, char *qname){
|
||||
int pos = 0;
|
||||
while (*buf++ != '\0') {
|
||||
if((*buf >= 'a' && *buf <= 'z') || (*buf >= 'A' && *buf <= 'Z'))
|
||||
*(qname + pos) = *buf;
|
||||
else if (*buf >= '0' && *buf <= '9')
|
||||
*(qname + pos) = *buf;
|
||||
else
|
||||
*(qname + pos) = '.';
|
||||
pos++;
|
||||
}
|
||||
qname[pos - 1] = '\0';
|
||||
}
|
||||
/*
|
||||
* This function read the event buf which contains the DNS answer section
|
||||
*/
|
||||
static void get_answer(struct event *s_event, uint16_t *class, uint16_t *type, uint16_t *size, uint32_t *ttl, int *pos){
|
||||
int p = *pos;
|
||||
|
||||
*type = s_event->buf[p++];
|
||||
*type |= s_event->buf[p++] << 8;
|
||||
|
||||
*class = s_event->buf[p++];
|
||||
*class |= s_event->buf[p++] << 8;
|
||||
|
||||
*ttl = s_event->buf[p++];
|
||||
*ttl |= s_event->buf[p++] << 8;
|
||||
*ttl |= s_event->buf[p++] << 16;
|
||||
*ttl |= s_event->buf[p++] << 24;
|
||||
|
||||
*size = s_event->buf[p++];
|
||||
*size |= s_event->buf[p++] << 8;
|
||||
|
||||
*pos = p;
|
||||
}
|
||||
/*
|
||||
* This function save to rsyslog format the answer section
|
||||
*/
|
||||
static void answer_to_log(struct event *s_event){
|
||||
int pos = 0;
|
||||
for (int i = 0; i < s_event->numAns; i++){
|
||||
char *s_class, *s_type;
|
||||
uint16_t i_type, i_class, i_size;
|
||||
uint32_t i_ttl;
|
||||
char b_class[16], b_type[16], b_ttl[16];
|
||||
/*
|
||||
* According to the RFC 1035 (https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.4)
|
||||
* In the section 4.1.4, message compression, the first two bits are set at 11 (0xc),
|
||||
* that's means, it's a pointer.
|
||||
* For instance, the two bytes 0xc00c, 0xc (11) it's the pointer and 0x00c is the position in the DNS header
|
||||
*/
|
||||
uint16_t msg = s_event->buf[pos++];
|
||||
msg |= s_event->buf[pos++] << 8;
|
||||
|
||||
header_to_log(s_event);
|
||||
|
||||
get_answer(s_event, &i_class, &i_type, &i_size, &i_ttl, &pos);
|
||||
|
||||
i_type = ntohs(i_type);
|
||||
i_class = ntohs(i_class);
|
||||
i_ttl = ntohl(i_ttl);
|
||||
i_size = ntohs(i_size);
|
||||
|
||||
s_class = mapClass(i_class);
|
||||
snprintf(b_class, 16, "class=%s;", s_class);
|
||||
fwrite(b_class, strlen(b_class), 1, f);
|
||||
free(s_class);
|
||||
|
||||
s_type = mapType(i_type);
|
||||
snprintf(b_type, 16, "type=%s;", s_type);
|
||||
fwrite(b_type, strlen(b_type), 1, f);
|
||||
free(s_type);
|
||||
|
||||
snprintf(b_ttl, 16, "ttl=%d;", i_ttl);
|
||||
fwrite(b_ttl, strlen(b_ttl), 1, f);
|
||||
|
||||
// Decode data
|
||||
if (i_type == 1) { // -> A
|
||||
uint32_t ip = s_event->buf[pos] + (s_event->buf[pos+1] << 8) + (s_event->buf[pos+2] << 16) + (s_event->buf[pos+3] << 24);
|
||||
char s_ip[36];
|
||||
snprintf(s_ip, 36, "ip=%s;", inet_ntoa(*(struct in_addr*)&ip));
|
||||
fwrite(s_ip, strlen(s_ip), 1, f);
|
||||
}
|
||||
if (i_type == 5) { // -> CNAME
|
||||
char cname[i_size];
|
||||
char buf[i_size + 7]; // 7 -> cname=;
|
||||
get_labels(s_event->buf + pos, cname);
|
||||
snprintf(buf, i_size + 7, "cname=%s;", cname);
|
||||
fwrite(buf, strlen(buf), 1, f);
|
||||
}
|
||||
if (i_type == 28){ // -> AAAA
|
||||
char buf[128];
|
||||
snprintf(buf, 128, "ipv6=%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%x%x;",
|
||||
s_event->buf[pos], s_event->buf[pos + 1],
|
||||
s_event->buf[pos + 2], s_event->buf[pos + 3],
|
||||
s_event->buf[pos + 4], s_event->buf[pos + 5],
|
||||
s_event->buf[pos + 6], s_event->buf[pos + 7],
|
||||
s_event->buf[pos + 8], s_event->buf[pos + 9],
|
||||
s_event->buf[pos + 10], s_event->buf[pos + 11],
|
||||
s_event->buf[pos + 12], s_event->buf[pos + 13],
|
||||
s_event->buf[pos + 14], s_event->buf[pos + 15]);
|
||||
fwrite(buf, strlen(buf), 1, f);
|
||||
}
|
||||
|
||||
pos += i_size;
|
||||
|
||||
fwrite("\n", 1, 1, f);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* This function print to the stdout the answer section
|
||||
*/
|
||||
static void print_answer(struct event *s_event){
|
||||
char *req_type;
|
||||
int pos = 0;
|
||||
char t[32];
|
||||
time_t ts = time(NULL);
|
||||
|
||||
for (int i = 0; i < s_event->numAns; i++){
|
||||
uint16_t type, class, size;
|
||||
char *s_type, *s_class;
|
||||
uint32_t ttl;
|
||||
uint16_t msg = s_event->buf[pos++];
|
||||
msg |= s_event->buf[pos++] << 8;
|
||||
|
||||
/* Print answer hdr */
|
||||
syslog_time(ts, t, sizeof(t));
|
||||
printf("%-20s", t);
|
||||
|
||||
req_type = mapReqType(s_event->req_type);
|
||||
printf("%s ", req_type);
|
||||
free(req_type);
|
||||
|
||||
printf("%5s:%5d\t", inet_ntoa(*(struct in_addr*)&s_event->client), s_event->dport);
|
||||
printf("%x\t", s_event->tid);
|
||||
|
||||
get_answer(s_event, &class, &type, &size, &ttl, &pos);
|
||||
|
||||
type = ntohs(type);
|
||||
class = ntohs(class);
|
||||
ttl = ntohl(ttl);
|
||||
size = ntohs(size);
|
||||
|
||||
/* Print answer data */
|
||||
s_class = mapClass(class);
|
||||
printf("%-5s", s_class);
|
||||
free(s_class);
|
||||
|
||||
s_type = mapType(type);
|
||||
printf("%s\t", s_type);
|
||||
free(s_type);
|
||||
|
||||
if (type == 1) { // -> A
|
||||
uint32_t ip = s_event->buf[pos] + (s_event->buf[pos+1] << 8) + (s_event->buf[pos+2] << 16) + (s_event->buf[pos+3] << 24);
|
||||
printf("%s %5d", inet_ntoa(*(struct in_addr*)&ip), ttl);
|
||||
}
|
||||
if (type == 5) { // -> CNAME
|
||||
char cname[size];
|
||||
get_labels(s_event->buf + pos, cname);
|
||||
printf("%s %d ", cname, ttl);
|
||||
}
|
||||
if (type == 28){ // -> AAAA
|
||||
for (int i = 0; i < size; i++){
|
||||
if (i % 2 == 0)
|
||||
printf("%x", s_event->buf[pos]);
|
||||
else{
|
||||
if (i < (size - 1))
|
||||
printf("%x:", s_event->buf[pos]);
|
||||
else
|
||||
printf("%x", s_event->buf[pos]);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
printf(" %d ", ttl);
|
||||
}
|
||||
pos += size;
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
/*
|
||||
* This function is called when a new event is pushed in the ring buffer from the eBPf program
|
||||
*/
|
||||
int handle_event(void *ctx, void *data, size_t data_sz){
|
||||
struct event *s_event = (struct event*)data;
|
||||
if (s_event->req_type == REQ_QUERY){
|
||||
if (arguments.to_output == 1)
|
||||
print_query(s_event);
|
||||
if (arguments.filename != NULL && f != NULL)
|
||||
query_to_log(s_event);
|
||||
}
|
||||
if (s_event->req_type == REQ_ANSWER){
|
||||
if (arguments.to_output == 1)
|
||||
print_answer(s_event);
|
||||
if (arguments.filename != NULL && f != NULL)
|
||||
answer_to_log(s_event);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
int main(int argc, char *argv[]){
|
||||
const char *fileObj = "src/dns-trace.ebpf.o";
|
||||
struct bpf_object *obj;
|
||||
struct bpf_program *programSkb;
|
||||
struct ring_buffer *rb;
|
||||
int err;
|
||||
int fd_map_data;
|
||||
int sock;
|
||||
|
||||
arguments = parse_args(argc, argv); // Parsing arguments
|
||||
|
||||
if (arguments.interface == NULL){
|
||||
printf("You must specified the interface name\n");
|
||||
exit(-1);
|
||||
}
|
||||
printf("Listen to %s\n", arguments.interface);
|
||||
|
||||
sock = create_rsock(arguments.interface);
|
||||
if (sock == -1){
|
||||
printf("Failed to listen to the interface %s\n", arguments.interface);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (arguments.filename != NULL){
|
||||
f = fopen(arguments.filename, "a");
|
||||
if (f == NULL){
|
||||
printf("Failed to create the file %s\n", arguments.filename);
|
||||
return -1;
|
||||
}
|
||||
printf("Save to %s\n", arguments.filename);
|
||||
}
|
||||
|
||||
signal(SIGINT, signalHandler);
|
||||
|
||||
/* Open and load our eBPF object */
|
||||
obj = bpf_object__open_file(fileObj, NULL);
|
||||
if (!obj){
|
||||
printf("Failed to open the file\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
err = bpf_object__load(obj);
|
||||
if (err){
|
||||
printf("Failed to load object\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Retrieving fd of maps */
|
||||
fd_map_data = bpf_object__find_map_fd_by_name(obj, "m_data");
|
||||
if (!fd_map_data){
|
||||
printf("Failed to find the fd map data\n");
|
||||
bpf_object__close(obj);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Retrieving our programs */
|
||||
programSkb = bpf_object__find_program_by_name(obj, "detect_dns");
|
||||
|
||||
if (!programSkb){
|
||||
printf("Failed to find program\n");
|
||||
bpf_object__close(obj);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bpf_program__attach(programSkb);
|
||||
int prog_fd = bpf_program__fd(programSkb);
|
||||
|
||||
setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(int));
|
||||
|
||||
/* Get the hostname of the system */
|
||||
get_hostname();
|
||||
|
||||
/* Start the ringbuffer */
|
||||
rb = ring_buffer__new(fd_map_data, handle_event, NULL, NULL);
|
||||
if (!rb){
|
||||
printf("Failed to create the ringbuf\n");
|
||||
bpf_object__close(obj);
|
||||
return -1;
|
||||
}
|
||||
|
||||
while(running){
|
||||
err = ring_buffer__poll(rb, 100 /* timeout, ms */);
|
||||
if (err == -EINTR){
|
||||
printf("Failed to get the ringbuf\n");
|
||||
running = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (f != NULL)
|
||||
fclose(f);
|
||||
|
||||
ring_buffer__free(rb);
|
||||
bpf_object__close(obj);
|
||||
|
||||
return 0;
|
||||
}
|
233
src/dns-trace.ebpf.c
Normal file
233
src/dns-trace.ebpf.c
Normal file
@ -0,0 +1,233 @@
|
||||
#define BPF_NO_GLOBAL_DATA
|
||||
#define __TARGET_ARCH_x86
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/udp.h>
|
||||
#include <linux/net.h>
|
||||
#include <linux/socket.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/net.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/ip.h>
|
||||
#include "common.h"
|
||||
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 256 * 1024 /* 256kb */);
|
||||
} m_data SEC(".maps");
|
||||
|
||||
static size_t get_labels(struct __sk_buff *skb, size_t offset, struct event *s_event){
|
||||
char c;
|
||||
int qname_len = 0;
|
||||
|
||||
bpf_skb_load_bytes(skb, offset, &c, 1); // Get the first byte, which is the length
|
||||
int pos = 1;
|
||||
/*
|
||||
* The qname is composed by a the number of bytes then follow by the label
|
||||
* For instance, for the qname www.bucchino.org,
|
||||
* the first byte is the number of byte, here, it's 3, then we have www (in hex)
|
||||
* Then, we have the byte of 8 and follow by the label bucchino (size 8)
|
||||
* And to finish, we have 3 follow by org and we finish with the \0 character
|
||||
* For instance, the result is:
|
||||
* 03 77 77 77 08 62 75 63 63 68 69 6e 6f 03 6f 72 67 00
|
||||
*/
|
||||
while (c != '\0') {
|
||||
bpf_skb_load_bytes(skb, offset + pos++, &c, 1);
|
||||
|
||||
if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
|
||||
s_event->qname[qname_len] = c;
|
||||
else if(c >= '0' && c <= '9')
|
||||
s_event->qname[qname_len] = c;
|
||||
else
|
||||
s_event->qname[qname_len] = '.';
|
||||
qname_len++;
|
||||
if (pos == 128 || c == '\0')
|
||||
break;
|
||||
}
|
||||
s_event->qname[qname_len - 1] = '\0';
|
||||
qname_len++;
|
||||
// bpf_printk("qname len: %d", qname_len);
|
||||
return qname_len;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function get the query field and the return the length of it
|
||||
*/
|
||||
static size_t get_query_section(struct __sk_buff *skb, struct event *s_event, uint8_t offset){
|
||||
size_t len;
|
||||
size_t qname_len = 0; // Full length of the qname field
|
||||
uint16_t class, type;
|
||||
|
||||
offset += sizeof(struct dnshdr);
|
||||
qname_len = get_labels(skb, offset, s_event);
|
||||
|
||||
// Get class and type
|
||||
len = qname_len;
|
||||
bpf_skb_load_bytes(skb, offset + qname_len, &type, sizeof(uint16_t));
|
||||
s_event->type = ntohs(type);
|
||||
len += 2;
|
||||
bpf_skb_load_bytes(skb, offset + qname_len + 2, &class, sizeof(uint16_t));
|
||||
s_event->class = ntohs(class);
|
||||
len += 2;
|
||||
|
||||
return len;
|
||||
}
|
||||
/*
|
||||
* https://datatracker.ietf.org/doc/html/rfc1035
|
||||
*/
|
||||
static int dnsquery(struct __sk_buff *skb, struct ethhdr eth, struct iphdr ip, struct udphdr udp, int dport, int sport){
|
||||
struct event *s_event;
|
||||
struct dnshdr dns = {0};
|
||||
|
||||
/* Get DNS header */
|
||||
bpf_skb_load_bytes(skb, sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr), &dns, sizeof(struct dnshdr));
|
||||
|
||||
// Check OpCode
|
||||
uint16_t qr = ntohs(dns.flags) & 0xF000; // Get the QR code: 0 -> query, 1 -> response
|
||||
|
||||
/* If it's not a query, we do not continue */
|
||||
if(qr != 0x0)
|
||||
return 0;
|
||||
|
||||
if (ntohs(dns.nbQuestions) == 0)
|
||||
return 0;
|
||||
|
||||
s_event = bpf_ringbuf_reserve(&m_data, sizeof(*s_event), 0);
|
||||
if (!s_event)
|
||||
return 0;
|
||||
|
||||
s_event->req_type = REQ_QUERY;
|
||||
|
||||
/* Get IP header */
|
||||
s_event->client = ip.saddr;
|
||||
|
||||
s_event->tid = ntohs(dns.transactionID);
|
||||
|
||||
/* Get the query section */
|
||||
uint8_t tlen = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr);
|
||||
get_query_section(skb, s_event, tlen);
|
||||
|
||||
// https://docs.cilium.io/en/stable/reference-guides/bpf/progtypes/
|
||||
s_event->dport = dport;
|
||||
s_event->sport = sport;
|
||||
|
||||
bpf_ringbuf_submit(s_event, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function read the dns answer section
|
||||
* All the answer is store in the buf variable and handle in the user space
|
||||
*/
|
||||
static void dnsanswer(struct __sk_buff *skb, struct iphdr ip, struct udphdr udp, int dport, int sport){
|
||||
struct event *s_event;
|
||||
struct dnshdr dns;
|
||||
uint32_t offset = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr);
|
||||
size_t tlen = ntohs(udp.len);
|
||||
unsigned int index = 0U;
|
||||
|
||||
if (tlen < 0 || tlen >= 256)
|
||||
return;
|
||||
|
||||
// Load dns header
|
||||
if (bpf_skb_load_bytes(skb, offset, &dns, sizeof(struct dnshdr)) < 0)
|
||||
return;
|
||||
|
||||
// Check OpCode
|
||||
uint16_t qr = ntohs(dns.flags) & 0xF000; // Get the QR code: 0 -> query, 1 -> response
|
||||
if(qr != 0x8000) // Not a response, we do not continue
|
||||
return;
|
||||
|
||||
if (ntohs(dns.nbQuestions) == 0)
|
||||
return;
|
||||
|
||||
s_event = bpf_ringbuf_reserve(&m_data, sizeof(*s_event), 0);
|
||||
if (!s_event)
|
||||
return;
|
||||
|
||||
s_event->req_type = REQ_ANSWER;
|
||||
|
||||
/* Get IP header */
|
||||
s_event->client = ip.daddr;
|
||||
|
||||
s_event->dport = dport;
|
||||
s_event->sport = sport;
|
||||
|
||||
/* Get the Transaction ID */
|
||||
s_event->tid = ntohs(dns.transactionID);
|
||||
|
||||
/* Get the query section */
|
||||
size_t query_len = get_query_section(skb, s_event, offset);
|
||||
|
||||
/* Get answer section */
|
||||
uint16_t ans = ntohs(dns.nbAnswerRRs);
|
||||
if (ans <= 0){
|
||||
bpf_ringbuf_discard(s_event, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
s_event->numAns = ans;
|
||||
|
||||
/*
|
||||
* Load query and answers
|
||||
* It's a little dirty to do that, to load byte by byte,
|
||||
* otherwise, I have an issue with the eBPF verifier
|
||||
*/
|
||||
offset += sizeof(struct dnshdr) + query_len;
|
||||
for (index = 0; index < tlen || index == MAX_UDP_PAYLOAD; index++)
|
||||
bpf_skb_load_bytes(skb, offset + index, s_event->buf + index, 1);
|
||||
|
||||
bpf_ringbuf_submit(s_event, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* skb -> http://oldvger.kernel.org/~davem/skb_data.html
|
||||
*/
|
||||
SEC("socket")
|
||||
int detect_dns(struct __sk_buff *skb) {
|
||||
struct ethhdr eth = {0};
|
||||
struct iphdr ip = {0};
|
||||
struct udphdr udp = {0};
|
||||
__u32 h_proto, p;
|
||||
__u32 dport;
|
||||
__u32 sport;
|
||||
|
||||
|
||||
if (skb->len < sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr))
|
||||
return 0;
|
||||
|
||||
bpf_skb_load_bytes(skb, 0, ð, sizeof(struct ethhdr));
|
||||
p = eth.h_proto;
|
||||
if (ntohs(p) != ETH_P_IP)
|
||||
return 0;
|
||||
|
||||
bpf_skb_load_bytes(skb, sizeof(struct ethhdr), &ip, sizeof(struct iphdr));
|
||||
|
||||
h_proto = ip.protocol;
|
||||
// If not UDP packet
|
||||
if (h_proto != 17)
|
||||
return 0;
|
||||
|
||||
bpf_skb_load_bytes(skb, sizeof(struct ethhdr) + sizeof(struct iphdr), &udp, sizeof(struct udphdr));
|
||||
|
||||
if (udp.len == 0)
|
||||
return 0;
|
||||
|
||||
// Check if DNS port
|
||||
dport = ntohs(udp.dest);
|
||||
sport = ntohs(udp.source);
|
||||
|
||||
if (dport == 53)
|
||||
dnsquery(skb, eth, ip, udp, dport, sport);
|
||||
else if(sport == 53)
|
||||
dnsanswer(skb, ip, udp, dport, sport);
|
||||
|
||||
return 0;
|
||||
}
|
||||
char LICENSE[] SEC("license") = "GPL";
|
BIN
src/dns-trace.ebpf.o
Normal file
BIN
src/dns-trace.ebpf.o
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user