From 408ad6aef36b34ffad9ea7803b0d39501a410818 Mon Sep 17 00:00:00 2001 From: geoffrey Date: Wed, 22 Jan 2025 19:25:57 +0100 Subject: [PATCH] Update project --- dns-trace | Bin 18624 -> 18624 bytes dns10.pcap | Bin 0 -> 286 bytes src/common.h | 4 +- src/dns-trace.c | 7 +- src/dns-trace.ebpf.c | 122 ++++++++---- src/dns-trace.ebpf.c_bck | 381 -------------------------------------- src/dns-trace.ebpf.c_bck2 | 379 ------------------------------------- src/dns-trace.ebpf.o | Bin 29352 -> 39712 bytes src/skel.ebpf.h | 0 9 files changed, 93 insertions(+), 800 deletions(-) create mode 100644 dns10.pcap delete mode 100644 src/dns-trace.ebpf.c_bck delete mode 100644 src/dns-trace.ebpf.c_bck2 delete mode 100644 src/skel.ebpf.h diff --git a/dns-trace b/dns-trace index 3cc428f4eef65401d5043d330e8d2cbf368fc73f..cf30db0745ebf6c31336db2b5b2ca34ac898fae6 100755 GIT binary patch delta 1298 zcmZ8gVQ5=b7`^u`uO`8b1h!=fgEURj88nL}wADh!!ure$%+cCKZ5c|SR{b+JT@;2{ zCW+$n@wDSAh*aEge+Dv&PjpUQ3oVwykklWWLXa{P-&n`ex!R(w*K=ReT6TZjbKm!! zbH49>+&6iKPM)FTPU~zIYe#_ zl1m)xCZjOXAvQV*GQ#gmTVa)p7AdqraSwyAJ{3g-4}>xZ4R!8+^=&aOB)gDAH}mLb z9{Lgteia7KZ!$QrVX&iNFfWu(3Z;>n5$xRGqU)Nj)a=QAk62@}&o*pqK@De3xvDEy zZPNJ-Qc{qN95OYQ^zSU)_=|I0vAz)#yKIlm=#sCOquX}3{q3V_4=G;HG%Ztgb7Is0nUkG1>a7JEh7gY$k<<{k;_I^U%nk-BUvAE}l zo4jMfTQFy*Mn==m95hmY;Bls(&T7}vxvaL7KAhH8bE*;l8R3D0u{As(r)8Rfz*q7Y ztr_a-&7a8kv_@ZbvnN7*?4wZq<+0nGPs4Hd9nKekj{rXadh47!Fm3}F0oH)Mz&dak z`1K;^6(GIK`FY?;;55*4kMrMv&n-cZ7dVENBfwR**tUm`GJiNht1KJdLmy(t!#*lA zEgYnIb^-e+o5fyXH?cp=T#+Enu^9G?EE@@iBB*c(&Lq9f^R{$gYs;1_eeeM%*^ST< z#GhgsxX0LqNQS;+E=GJ#=81bc)_?sG(8S7MG@IA@&%YiUX8rLXJ<5*96Z8~27k`~r zO#j|x2m10R6WG&9pG3muQ1V|%azxos*J}vQb!F&j=I_1^_F{L5O05C*%H!|R8;tgp v=ppmXo}in~v2(o%S0&IWRbjvMW@wp(6vUcMRoI}??Iz)7Ghy>%rG3kPP9^SN delta 1277 zcmZ8hZD?Cn7(VB2S(DJ(1lNx{Rxj8Q9EpveLo^U3DTI&Zdg4 zMy(rMV`Sy^qxeHCn?L-atVT!uY73SXVM;s3KA?lbHo0z9(_vlEj^lH3Z&TTUoaf&6 zeV*q$_vPM9kMQXce#XgNJ#JQ5J>=zK7jsy5jN18>_gJlUS42{k?>6^dT^K*w|D5~4 zz^DB`?0fOhg~rcXPnpUlwb`aOL@k-q_S#L{Ri)~7x8uHUH#MT$XLmojuREAM=sM{1 zybUisQx+&d^qM1k!TXD&WJ+c?KcFjBTT`#Qz%aDDp6)!8*1p$`OUA6(^sf-Q;rP5> z_A0)RkZC%C;*=tlD|xijT7FUn)xi(Sf}!Q~;h`K$R_dAPW3V+tC{GBi-zGVTX%ImE z5LH848Xj6=1C=RoAC_FD$fY%Irl}4cDY;pYn8QOROV&bBBNcOZ3n>boUlrAz>*`8d z{S2B&w}^C$mcB&lPfGo>cBp}(L4{?oD9?RODl^d>#%G@RN5)=AYYWCX)P6;6LO#D~ zSUC!UG6)Rqd|EqSApKk<^-`C*E%hL%Y0dmb>b{j5#u~W?$*k$LLe3%N981P=!f|=( z{NL*j`8CuNa-6sKW0rH1z0v^EYp7cTT}@U)cvuQYA)IyWtdplOcH5nHY_HqP*zxJ) zj1)`Vx&L{0NpH#g>Bn*(i#@$36Rip`7TecuT#9wJ8!NFy%vkNzGa)C+PwlC{rw=bV z&Fk4Gww!g)$w1qYBe2)qb<9=I9aa`9L8GXAoM$^%|L zO8!8YU!i!QPHp%@2n?mTw&JQAzcLyL`1oNO$39B)*x#XL>=E(?eY}Snv7e=Q(C6QQ zBsECDVh?yuD36zI+EC3)OPs9WgnB)VfUDDZu+4D*DS4JZRPLk>rIb7&PvhDr-e*VH zU29+1u=bI!Rl6x3^6@@;Ar$6=G!}Y^C$j3!yBm3eypb?JMzKg0&ttF1_D60h%4(3h zng>yzZ*Joqq_$j!V7Vp5m7GdPAAOgP6W^8MHQ6_I`P_Vr##+O!#1`wc1YK=ye>N=JN7#j-=A$IN|^R literal 0 HcmV?d00001 diff --git a/src/common.h b/src/common.h index a6ad143..62f6a3f 100644 --- a/src/common.h +++ b/src/common.h @@ -22,7 +22,7 @@ struct dnshdr { };*/ struct event { - uint32_t saddr; + uint32_t client; int dport; int sport; uint16_t tid; @@ -30,7 +30,7 @@ struct event { char qname[QNAME_SIZE]; int class; int type; - char ans[32]; + uint32_t ans; }; struct query_section{ diff --git a/src/dns-trace.c b/src/dns-trace.c index 77ea100..543e060 100644 --- a/src/dns-trace.c +++ b/src/dns-trace.c @@ -191,7 +191,7 @@ static void mapType(const int type){ int handle_event(void *ctx, void *data, size_t data_sz){ struct event *s_event = (struct event*)data; - printf("IP: %s\n", inet_ntoa(*(struct in_addr*)&s_event->saddr)); + printf("IP: %s\n", inet_ntoa(*(struct in_addr*)&s_event->client)); printf("dport: %d\n", s_event->dport); printf("sport: %d\n", s_event->sport); printf("Transaction ID: %x\n", s_event->tid); @@ -202,7 +202,12 @@ int handle_event(void *ctx, void *data, size_t data_sz){ mapClass(s_event->class); printf("Type: "); mapType(s_event->type); + + if (s_event->req_type == REQ_ANSWER) + printf("Data: %s\n", inet_ntoa(*(struct in_addr*)&s_event->ans)); + printf("\n"); + return 0; } int main(int argc, char *argv[]){ diff --git a/src/dns-trace.ebpf.c b/src/dns-trace.ebpf.c index ca05106..3023a03 100644 --- a/src/dns-trace.ebpf.c +++ b/src/dns-trace.ebpf.c @@ -39,22 +39,24 @@ struct { static size_t get_labels2(struct __sk_buff *skb, size_t offset, struct event *s_event){ char c; int qname_len = 0; - bpf_skb_load_bytes(skb, offset + sizeof(struct dnshdr), &c, 1); + bpf_printk("labels off: %d", offset); + bpf_skb_load_bytes(skb, offset, &c, 1); // Get the first byte, which is the length int pos = 1; while (c != '\0') { - bpf_skb_load_bytes(skb, offset + 12 + pos++, &c, 1); + 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 s_event->qname[qname_len] = '.'; qname_len++; - if (pos == 128) + if (pos == 128 || c == '\0') break; } s_event->qname[qname_len - 1] = '\0'; qname_len++; - bpf_printk("qname len: %d", qname_len); + bpf_printk("qname: %s", s_event->qname); + // bpf_printk("qname len: %d", qname_len); return qname_len; } static size_t get_labels(struct __sk_buff *skb, size_t offset, size_t end, struct event *s_event, struct query_section *s_query){ @@ -101,14 +103,15 @@ static size_t get_query_section(struct __sk_buff *skb, struct event *s_event, ui size_t qname_len = 0; // Full length of the qname field uint16_t class, type; + offset += sizeof(struct dnshdr); qname_len = get_labels2(skb, offset, s_event); // Get class and type len = qname_len; - bpf_skb_load_bytes(skb, offset + 12 + qname_len, &type, sizeof(uint16_t)); + 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 + 12 + qname_len + 2, &class, sizeof(uint16_t)); + bpf_skb_load_bytes(skb, offset + qname_len + 2, &class, sizeof(uint16_t)); s_event->class = ntohs(class); len += 2; @@ -118,16 +121,60 @@ static size_t get_answer(struct __sk_buff *skb, struct event *s_event, size_t tl size_t len = 0; unsigned char buf[25] = {0}; // Need to be unsigned, otherwise, the result is fffff + // Get the 2 first bytes to identify if it's a message compression or not + bpf_printk("offset: %d", tlen); if(bpf_skb_load_bytes(skb, tlen, &buf, 2) < 0) return 0; + + tlen += 2; // Which is for the message compression /* * 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 it's the pointer and 0x00c is the position in the DNS header + * For instance, the two bytes 0xc00c, 0xc (11) it's the pointer and 0x00c is the position in the DNS header */ if (buf[0] == 0xc0){ - bpf_printk("Pointer to %x", buf[1]); + /* + * I cannot read the labels, because the eBPF verifier considerate as a infinity loop + */ + // bpf_printk("new offset: %d", sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + buf[1]); + // get_labels2(skb, sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + 12, s_event); + + /* + * According to the RFC 1035, the structure of answer is like that: + * https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.3 + */ + + // Get the class and type + uint16_t type, class; + uint32_t ttl; + bpf_printk("offset: %d", tlen); + bpf_skb_load_bytes(skb, tlen, &type, sizeof(uint16_t)); + bpf_printk("type: %d", ntohs(type)); + tlen += 2; + bpf_printk("offset: %d", tlen); + bpf_skb_load_bytes(skb, tlen, &class, sizeof(uint16_t)); + tlen += 4; + bpf_printk("offset: %d", tlen); + bpf_printk("class %d", ntohs(class)); + // Get ttl + bpf_skb_load_bytes(skb, tlen, &ttl, sizeof(uint32_t)); + tlen += 2; + bpf_printk("offset: %d", tlen); + bpf_printk("ttl: %d", ntohs(ttl)); + + // Get data size + uint16_t size; + bpf_skb_load_bytes(skb, tlen, &size, sizeof(uint16_t)); + bpf_printk("size: %d", ntohs(size)); + tlen += 2; + + uint32_t data; + bpf_skb_load_bytes(skb, tlen, &data, sizeof(uint32_t)); + s_event->ans = data; + } + else { + // get_labels2(skb, sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct dnshdr), s_event); } return len; @@ -138,7 +185,6 @@ static size_t get_answer(struct __sk_buff *skb, struct event *s_event, size_t tl 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}; - char saddr[32]; // bpf_printk("udp len: %d", ntohs(udp.len)); s_event = bpf_ringbuf_reserve(&m_data, sizeof(*s_event), 0); @@ -146,7 +192,7 @@ static int dnsquery(struct __sk_buff *skb, struct ethhdr eth, struct iphdr ip, s return 0; /* Get IP header */ - s_event->saddr = ip.saddr; + s_event->client = ip.saddr; /* Get DNS header */ bpf_skb_load_bytes(skb, sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr), &dns, sizeof(struct dnshdr)); @@ -165,19 +211,9 @@ static int dnsquery(struct __sk_buff *skb, struct ethhdr eth, struct iphdr ip, s return 0; } - s_event->tid = ntohs(dns.transactionID); // Use as key map - - //struct dns_query dquery; - //bpf_skb_load_bytes(skb, sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct dnshdr), &dquery, sizeof(struct dns_query)); - // bpf_printk("size: %d %d %d", tlen, skb->len, (skb->len - tlen)); - //dlen = (skb->len - tlen); - //bpf_printk("DNS packet len: %d", dlen); - //qlen = dlen - sizeof(struct dnshdr); - //bpf_printk("size: %d %d", sizeof(struct dnshdr), qlen); - + s_event->tid = ntohs(dns.transactionID); /* Get the query section */ - // uint8_t tlen = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct dnshdr); uint8_t tlen = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr); size_t query_len = get_query_section(skb, s_event, tlen); @@ -238,7 +274,7 @@ TODO: je recupere tout le skb->data, grace au skb->len grace a ca, j'aurai tout le payload udp et toute les donnees dns je pourrai facilement parcourir les data */ -static void dnsanswer(struct __sk_buff *skb, struct iphdr ip, struct udphdr udp, int dport, int sport){ +static void dnsanswer(struct __sk_buff *skb, struct iphdr ip, struct udphdr udp, int dport, int sport){ struct event *s_event; struct dnshdr dns; uint16_t tid = 0U; @@ -252,6 +288,9 @@ static void dnsanswer(struct __sk_buff *skb, struct iphdr ip, struct udphdr udp if (!s_event) return; + /* Get IP header */ + s_event->client = ip.daddr; + // Check OpCode uint16_t flags = ntohs(dns.flags); uint16_t qr = flags & 0xF000; // Get the QR code: 0 -> query, 1 -> response @@ -260,11 +299,14 @@ static void dnsanswer(struct __sk_buff *skb, struct iphdr ip, struct udphdr udp else if(qr == 0x8000) // Response s_event->req_type = REQ_ANSWER; - if (ntohs(dns.nbQuestions) == 0 && ntohs(dns.nbAnswerRRs)){ + if (ntohs(dns.nbQuestions) == 0){ bpf_ringbuf_discard(s_event, 0); return; } + s_event->dport = dport; + s_event->sport = sport; + /* Get the Transaction ID */ s_event->tid = ntohs(dns.transactionID); @@ -272,7 +314,26 @@ static void dnsanswer(struct __sk_buff *skb, struct iphdr ip, struct udphdr udp size_t query_len = get_query_section(skb, s_event, offset); /* Get answer section */ - for (int i = 0; i < ntohs(dns.nbAnswerRRs); i++){ + uint16_t ans = ntohs(dns.nbAnswerRRs); + if (ans < 0){ + bpf_ringbuf_discard(s_event, 0); + return; + } + + if (ntohs(dns.nbAnswerRRs) > 0){ + /* + * We get a least the 5 last answer + * In the RFC 1035 (https://datatracker.ietf.org/doc/html/rfc1035#section-4.2.1) the max udp payload is 512 bytes + * The program limit te size of the answer + */ + offset += sizeof(struct dnshdr) + query_len; // For the pos in the answer section in the skb + for (uint16_t i = 0; i < ans; i++){ + get_answer(skb, s_event, offset); + if (i == ans || i == 5) + break; + } + } + if (ntohs(dns.nbAuthorityRRs) > 0){ } /* @@ -292,20 +353,7 @@ static void dnsanswer(struct __sk_buff *skb, struct iphdr ip, struct udphdr udp 3 - dans le get query et get answer, on push dans le ring buffer et tout est store dans le struct event */ - /* Get the query response */ - /*uint8_t tlen = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct dnshdr); - uint16_t class, type; - // size_t query_len = get_query_section(skb, s_event, &class, &type, tlen); - size_t query_len = get_query_section(skb, s_event, &s_query, tlen); - bpf_printk("answer qname: %s", s_event->qname); - s_event->dport = dport; - s_event->sport = sport; - s_event->class = ntohs(class); - s_event->type = ntohs(type);*/ - /* Get the answer */ - //tlen += query_len; - //size_t answer_len = get_answer(skb, s_event, tlen); bpf_ringbuf_submit(s_event, 0); } diff --git a/src/dns-trace.ebpf.c_bck b/src/dns-trace.ebpf.c_bck deleted file mode 100644 index ca9a122..0000000 --- a/src/dns-trace.ebpf.c_bck +++ /dev/null @@ -1,381 +0,0 @@ -#define BPF_NO_GLOBAL_DATA -#define __TARGET_ARCH_x86 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "common.h" - - -/* - * Helper: - * Issue: invalid indirect read from stack R2 off - * Fix: check if all variables is initialised - * Issue: R1 invalid mem access 'inv' - * Fix: the value can be NULL - */ - -struct { - __uint(type, BPF_MAP_TYPE_RINGBUF); - __uint(max_entries, 256 * 1024 /* 256kb */); -} m_data SEC(".maps"); - -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, 32768); // pid_max -> https://linux.die.net/man/5/proc - __type(key, uint16_t); - __type(value, struct dns_answer); -} m_tid SEC(".maps"); - -static size_t get_labels(struct __sk_buff *skb, size_t offset, size_t end, struct event *s_event, struct query_section *s_query){ - //size_t len; - char buf[256] = {0}; - char *c; - int index = 0; - size_t qname_len = 0; // Full length of the qname field - - bpf_skb_load_bytes(skb, offset, &buf, 41); - c = buf; - - while (*(c++) != '\0') { - if(*c >= 'a' && *c <= 'z') - s_event->qname[index] = *c; - else if(*c >= 'A' && *c <= 'Z') - s_event->qname[index] = *c; - else - s_event->qname[index] = '.'; - index++; - qname_len++; - } - s_event->qname[--index] = '\0'; - qname_len++; // For the null character - - 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, uint16_t *class, uint16_t *type, uint8_t tlen){ -static size_t get_query_section(struct __sk_buff *skb, struct event *s_event, struct query_section *s_query, uint8_t offset){ - size_t len; - //char buf[256] = {0}; - //int index = 0; - int qname_len = 0; // Full length of the qname field - //char qname[QNAME_SIZE] = {0}; - //char *c; - uint8_t flen = skb->len; - /* - We get the size for the buffer - We substract the full size of the buffer (skb->len) with the sizes of each headers (eth + ip + udp + dns header) - */ - uint8_t l = flen - offset; - - if (l < 0) - return 0; - - //bpf_skb_load_bytes(skb, tlen, &buf, l); - //bpf_skb_load_bytes(skb, offset, &buf, 41); - //c = buf; - - // get_labels(struct __sk_buff *skb, size_t offset, size_t end, struct event *s_event, struct query_section *s_query) - qname_len = get_labels(skb, offset, 41, s_event, s_query); - /* - * 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') { - if(*c >= 'a' && *c <= 'z') - s_event->qname[index] = *c; - else if(*c >= 'A' && *c <= 'Z') - s_event->qname[index] = *c; - else - s_event->qname[index] = '.'; - index++; - qname_len++; - } - s_event->qname[--index] = '\0'; - qname_len++; // For the null character*/ - // bpf_printk("l: %d", l); - - // Get class and type - len = qname_len; - bpf_skb_load_bytes(skb, offset + qname_len, &s_query->type, sizeof(uint16_t)); - len += 2; - bpf_skb_load_bytes(skb, offset + qname_len + 2, &s_query->class, sizeof(uint16_t)); - len += 2; - - return len; -} -static size_t get_answer(struct __sk_buff *skb, struct event *s_event, size_t tlen){ - size_t len = 0; - unsigned char buf[25] = {0}; // Need to be unsigned, otherwise, the result is fffff - - if(bpf_skb_load_bytes(skb, tlen, &buf, 2) < 0) - return 0; - /* - * 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 it's the pointer and 0x00c is the position in the DNS header - */ - if (buf[0] == 0xc0){ - bpf_printk("Pointer to %x", buf[1]); - } - - 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}; - char saddr[32]; - struct query_section s_query = {0}; - // bpf_printk("udp len: %d", ntohs(udp.len)); - - s_event = bpf_ringbuf_reserve(&m_data, sizeof(*s_event), 0); - if (!s_event) - return 0; - - /* Get IP header */ - s_event->saddr = ip.saddr; - - /* 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 flags = ntohs(dns.flags); - uint16_t qr = flags & 0xF000; // Get the QR code: 0 -> query, 1 -> response - if (qr == 0x0) - bpf_printk("Query"); - else if(qr == 0x8000) - bpf_printk("Response"); - bpf_printk("Flags: %x %x", flags, qr); - - if (ntohs(dns.nbQuestions) == 0){ - bpf_ringbuf_discard(s_event, 0); - return 0; - } - - bpf_printk("tid: %x", ntohs(dns.transactionID)); // Use as key map - bpf_printk("nb question: %d", ntohs(dns.nbQuestions)); - - //struct dns_query dquery; - //bpf_skb_load_bytes(skb, sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct dnshdr), &dquery, sizeof(struct dns_query)); - // bpf_printk("size: %d %d %d", tlen, skb->len, (skb->len - tlen)); - //dlen = (skb->len - tlen); - //bpf_printk("DNS packet len: %d", dlen); - //qlen = dlen - sizeof(struct dnshdr); - //bpf_printk("size: %d %d", sizeof(struct dnshdr), qlen); - - - /* Get the query structure */ - uint8_t tlen = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct dnshdr); - // size_t query_len = get_query_section(skb, s_event, &class, &type, tlen); - size_t query_len = get_query_section(skb, s_event, &s_query, tlen); - - // https://docs.cilium.io/en/stable/reference-guides/bpf/progtypes/ - s_event->dport = dport; - s_event->sport = sport; - s_event->class = ntohs(s_query.class); - s_event->type = ntohs(s_query.type); - //if(bpf_probe_read_user_str(&s_event->qname, sizeof(s_event->qname), qname) < 0) - // bpf_printk("Failed to copy qname"); - - // Add to map - - bpf_ringbuf_submit(s_event, 0); - - return 0; -} - -/* -TODO: je recupere tout le skb->data, grace au skb->len -grace a ca, j'aurai tout le payload udp et toute les donnees dns -je pourrai facilement parcourir les data -*/ -static int dnsanswer(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}; - uint16_t tid = 0; - //struct dns_answer s_dnsanswer; - //struct query_section s_query = {0}; - unsigned char buf[256] = {0}; - uint32_t offset = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr); - uint32_t skblen = skb->len; - uint32_t l; - - if (skblen < 1 || skblen == 0) - return 0; - - bpf_printk("skblen: %d", skb->len); - if (skb->len <= -1) { - bpf_printk("skblen: %d", skb->len); - skblen = 12; // Get the 12 first bytes - } - - bpf_printk("%d %d", skblen, offset); - if (skblen < offset) - return 0; - l = skblen - offset; - - - - bpf_printk("size: %d %d", skb->len, l); - // if (bpf_skb_load_bytes_relative(skb, offset, &buf, l, BPF_HDR_START_MAC) < 0){ - if (bpf_skb_load_bytes(skb, offset, &buf, l) < 0){ - bpf_printk("Failed"); - return 0; - } - s_event = bpf_ringbuf_reserve(&m_data, sizeof(*s_event), 0); - if (!s_event) - return 0; - /* Get Transaction ID */ - //tid = (uint16_t)buf >> 8; - - //bpf_printk("ttid: %x", ntohs(tid)); - - /* 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 flags = ntohs(dns.flags); - uint16_t qr = flags & 0xF000; // Get the QR code: 0 -> query, 1 -> response - if (qr == 0x0){} // Query - else if(qr == 0x8000){} // Response - - if (ntohs(dns.nbQuestions) == 0 && ntohs(dns.nbAnswerRRs)){ - bpf_ringbuf_discard(s_event, 0); - return 0; - }*/ - - /* Get the Transaction ID */ - //tid = ntohs(dns.transactionID); - //bpf_printk("tid: %x", tid); - - /* - * In the user space, if the haven't have the answer, we can have an error - * The solution is to push to the ring buffer and the answer is store in - * the struct event - * Or, we push to the ring buffer and the query only with the map - * but, if we haven't have the answer, we need print the query - */ - - /* - Pour recuperer les infos: - 1 - dans le getquery, on push dans le ringbuffer et dans le userspace, on recupere aussi la reponse - mais si la reponse, nous l'avons pas encore, ca fail et dans le get answer on push dans une map - 2 - on push dans le ring buffer quand on a la reponse avec la requette car c'est dans le field query - cependant, si on a pas la reponse, on n'aura jamais la query - 3 - dans le get query et get answer, on push dans le ring buffer et tout est store dans le struct event - */ - - /* Get the query response */ - /*uint8_t tlen = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct dnshdr); - uint16_t class, type; - // size_t query_len = get_query_section(skb, s_event, &class, &type, tlen); - size_t query_len = get_query_section(skb, s_event, &s_query, tlen); - bpf_printk("answer qname: %s", s_event->qname); - s_event->dport = dport; - s_event->sport = sport; - s_event->class = ntohs(class); - s_event->type = ntohs(type);*/ - - /* Get the answer */ - //tlen += query_len; - //size_t answer_len = get_answer(skb, s_event, tlen); - bpf_ringbuf_submit(s_event, 0); - - return 0; -} -SEC("socket") -int detect_dns(struct __sk_buff *skb) { - //void *data = (void *)(long)skb->data; - //void *data_end = (void *)(long)skb->data_end; - //struct ethhdr *eth = data; - struct ethhdr eth = {0}; - struct iphdr ip = {0}; - struct udphdr udp = {0}; - unsigned long long h_proto, p; - unsigned long long dport; - unsigned long long sport; - - //if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) > data_end) - // return 0; - - //bpf_skb_load_bytes(skb, 12, &p, 2); - bpf_skb_load_bytes(skb, 0, ð, sizeof(struct ethhdr)); - p = eth.h_proto; - if (ntohs(p) != ETH_P_IP) - return 0; - - // bpf_printk("ip: %d",ntohs(p)); - - //ip = (struct iphdr*)(data + sizeof(struct ethhdr)); - bpf_skb_load_bytes(skb, sizeof(struct ethhdr), &ip, sizeof(struct iphdr)); - - //bpf_skb_load_bytes(data, sizeof(struct ethhdr), ip, sizeof(struct ip)); - h_proto = ip.protocol; - // If not UDP packet - if (h_proto != 17) - return 0; - - // bpf_printk("proto: %d", h_proto); - //udp = (struct udphdr*)(data + sizeof(struct ethhdr) + sizeof(struct iphdr)); - - bpf_skb_load_bytes(skb, sizeof(struct ethhdr) + sizeof(struct iphdr), &udp, sizeof(struct udphdr)); - - // 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, eth, ip, udp, dport, sport); - - return 0; -} -/*SEC("xdp") -int detect_dns(struct xdp_md *ctx){ - void *data_end = (void *)(long)ctx->data_end; - void *data = (void *)(long)ctx->data; - struct ethhdr *eth = data; - struct iphdr *ip; - struct udphdr *udp; - __u16 h_proto; - __u16 dport; - - if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) > data_end) - return XDP_DROP; - - ip = (struct iphdr*)(data + sizeof(struct ethhdr)); - udp = (struct udphdr*)(data + sizeof(struct ethhdr) + sizeof(struct iphdr)); - - h_proto = ip->protocol; - // If not UDP packet - if (h_proto != 17) - return XDP_PASS; - - // Check if DNS port - dport = udp->dest; - bpf_printk("Dport: %d", (dport)); - - return XDP_PASS; -}*/ -char LICENSE[] SEC("license") = "GPL"; diff --git a/src/dns-trace.ebpf.c_bck2 b/src/dns-trace.ebpf.c_bck2 deleted file mode 100644 index 6c32116..0000000 --- a/src/dns-trace.ebpf.c_bck2 +++ /dev/null @@ -1,379 +0,0 @@ -#define BPF_NO_GLOBAL_DATA -#define __TARGET_ARCH_x86 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "common.h" - - -/* - * Helper: - * Issue: invalid indirect read from stack R2 off - * Fix: check if all variables is initialised - * Issue: R1 invalid mem access 'inv' - * Fix: the value can be NULL - */ - -struct { - __uint(type, BPF_MAP_TYPE_RINGBUF); - __uint(max_entries, 256 * 1024 /* 256kb */); -} m_data SEC(".maps"); - -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __uint(max_entries, 32768); // pid_max -> https://linux.die.net/man/5/proc - __type(key, uint16_t); - __type(value, struct dns_answer); -} m_tid SEC(".maps"); - -static size_t get_labels(struct __sk_buff *skb, size_t offset, size_t end, struct event *s_event, struct query_section *s_query){ - //size_t len; - char buf[256] = {0}; - char *c; - int index = 0; - size_t qname_len = 0; // Full length of the qname field - - bpf_skb_load_bytes(skb, offset, &buf, 41); - c = buf; - - while (*(c++) != '\0') { - if(*c >= 'a' && *c <= 'z') - s_event->qname[index] = *c; - else if(*c >= 'A' && *c <= 'Z') - s_event->qname[index] = *c; - else - s_event->qname[index] = '.'; - index++; - qname_len++; - } - s_event->qname[--index] = '\0'; - qname_len++; // For the null character - - 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, uint16_t *class, uint16_t *type, uint8_t tlen){ -static size_t get_query_section(struct __sk_buff *skb, struct event *s_event, struct query_section *s_query, uint8_t offset){ - size_t len; - //char buf[256] = {0}; - //int index = 0; - int qname_len = 0; // Full length of the qname field - //char qname[QNAME_SIZE] = {0}; - //char *c; - uint8_t flen = skb->len; - /* - We get the size for the buffer - We substract the full size of the buffer (skb->len) with the sizes of each headers (eth + ip + udp + dns header) - */ - uint8_t l = flen - offset; - - if (l < 0) - return 0; - - //bpf_skb_load_bytes(skb, tlen, &buf, l); - //bpf_skb_load_bytes(skb, offset, &buf, 41); - //c = buf; - - // get_labels(struct __sk_buff *skb, size_t offset, size_t end, struct event *s_event, struct query_section *s_query) - qname_len = get_labels(skb, offset, 41, s_event, s_query); - /* - * 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') { - if(*c >= 'a' && *c <= 'z') - s_event->qname[index] = *c; - else if(*c >= 'A' && *c <= 'Z') - s_event->qname[index] = *c; - else - s_event->qname[index] = '.'; - index++; - qname_len++; - } - s_event->qname[--index] = '\0'; - qname_len++; // For the null character*/ - // bpf_printk("l: %d", l); - - // Get class and type - len = qname_len; - bpf_skb_load_bytes(skb, offset + qname_len, &s_query->type, sizeof(uint16_t)); - len += 2; - bpf_skb_load_bytes(skb, offset + qname_len + 2, &s_query->class, sizeof(uint16_t)); - len += 2; - - return len; -} -static size_t get_answer(struct __sk_buff *skb, struct event *s_event, size_t tlen){ - size_t len = 0; - unsigned char buf[25] = {0}; // Need to be unsigned, otherwise, the result is fffff - - if(bpf_skb_load_bytes(skb, tlen, &buf, 2) < 0) - return 0; - /* - * 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 it's the pointer and 0x00c is the position in the DNS header - */ - if (buf[0] == 0xc0){ - bpf_printk("Pointer to %x", buf[1]); - } - - 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}; - char saddr[32]; - struct query_section s_query = {0}; - // bpf_printk("udp len: %d", ntohs(udp.len)); - - s_event = bpf_ringbuf_reserve(&m_data, sizeof(*s_event), 0); - if (!s_event) - return 0; - - /* Get IP header */ - s_event->saddr = ip.saddr; - - /* 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 flags = ntohs(dns.flags); - uint16_t qr = flags & 0xF000; // Get the QR code: 0 -> query, 1 -> response - if (qr == 0x0) - bpf_printk("Query"); - else if(qr == 0x8000) - bpf_printk("Response"); - bpf_printk("Flags: %x %x", flags, qr); - - if (ntohs(dns.nbQuestions) == 0){ - bpf_ringbuf_discard(s_event, 0); - return 0; - } - - bpf_printk("tid: %x", ntohs(dns.transactionID)); // Use as key map - bpf_printk("nb question: %d", ntohs(dns.nbQuestions)); - - //struct dns_query dquery; - //bpf_skb_load_bytes(skb, sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct dnshdr), &dquery, sizeof(struct dns_query)); - // bpf_printk("size: %d %d %d", tlen, skb->len, (skb->len - tlen)); - //dlen = (skb->len - tlen); - //bpf_printk("DNS packet len: %d", dlen); - //qlen = dlen - sizeof(struct dnshdr); - //bpf_printk("size: %d %d", sizeof(struct dnshdr), qlen); - - - /* Get the query structure */ - uint8_t tlen = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct dnshdr); - // size_t query_len = get_query_section(skb, s_event, &class, &type, tlen); - size_t query_len = get_query_section(skb, s_event, &s_query, tlen); - - // https://docs.cilium.io/en/stable/reference-guides/bpf/progtypes/ - s_event->dport = dport; - s_event->sport = sport; - s_event->class = ntohs(s_query.class); - s_event->type = ntohs(s_query.type); - //if(bpf_probe_read_user_str(&s_event->qname, sizeof(s_event->qname), qname) < 0) - // bpf_printk("Failed to copy qname"); - - // Add to map - - bpf_ringbuf_submit(s_event, 0); - - return 0; -} - -/* -TODO: je recupere tout le skb->data, grace au skb->len -grace a ca, j'aurai tout le payload udp et toute les donnees dns -je pourrai facilement parcourir les data -*/ -static int dnsanswer(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}; - uint16_t tid = 0; - //struct dns_answer s_dnsanswer; - //struct query_section s_query = {0}; - unsigned char buf[256] = {0}; - uint32_t offset = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr); - uint32_t skblen = skb->len; - uint32_t l; - - if (skblen < 1 || skblen == 0) - return 0; - - bpf_printk("skblen: %d", skb->len); - if (skb->len <= -1) { - bpf_printk("skblen: %d", skb->len); - skblen = 12; // Get the 12 first bytes - } - - bpf_printk("%d %d", skblen, offset); - if (skblen < offset) - return 0; - l = skblen - offset; - - - - bpf_printk("size: %d %d", skb->len, l); - // if (bpf_skb_load_bytes_relative(skb, offset, &buf, l, BPF_HDR_START_MAC) < 0){ - if (bpf_skb_load_bytes(skb, offset, &buf, l) < 0){ - bpf_printk("Failed"); - return 0; - } - s_event = bpf_ringbuf_reserve(&m_data, sizeof(*s_event), 0); - if (!s_event) - return 0; - /* Get Transaction ID */ - //tid = (uint16_t)buf >> 8; - - //bpf_printk("ttid: %x", ntohs(tid)); - - /* 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 flags = ntohs(dns.flags); - uint16_t qr = flags & 0xF000; // Get the QR code: 0 -> query, 1 -> response - if (qr == 0x0){} // Query - else if(qr == 0x8000){} // Response - - if (ntohs(dns.nbQuestions) == 0 && ntohs(dns.nbAnswerRRs)){ - bpf_ringbuf_discard(s_event, 0); - return 0; - }*/ - - /* Get the Transaction ID */ - //tid = ntohs(dns.transactionID); - //bpf_printk("tid: %x", tid); - - /* - * In the user space, if the haven't have the answer, we can have an error - * The solution is to push to the ring buffer and the answer is store in - * the struct event - * Or, we push to the ring buffer and the query only with the map - * but, if we haven't have the answer, we need print the query - */ - - /* - Pour recuperer les infos: - 1 - dans le getquery, on push dans le ringbuffer et dans le userspace, on recupere aussi la reponse - mais si la reponse, nous l'avons pas encore, ca fail et dans le get answer on push dans une map - 2 - on push dans le ring buffer quand on a la reponse avec la requette car c'est dans le field query - cependant, si on a pas la reponse, on n'aura jamais la query - 3 - dans le get query et get answer, on push dans le ring buffer et tout est store dans le struct event - */ - - /* Get the query response */ - /*uint8_t tlen = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct dnshdr); - uint16_t class, type; - // size_t query_len = get_query_section(skb, s_event, &class, &type, tlen); - size_t query_len = get_query_section(skb, s_event, &s_query, tlen); - bpf_printk("answer qname: %s", s_event->qname); - s_event->dport = dport; - s_event->sport = sport; - s_event->class = ntohs(class); - s_event->type = ntohs(type);*/ - - /* Get the answer */ - //tlen += query_len; - //size_t answer_len = get_answer(skb, s_event, tlen); - bpf_ringbuf_submit(s_event, 0); - - return 0; -} -SEC("socket") -int detect_dns(struct __sk_buff *skb) { - void *data = (void *)(long)skb->data; - void *data_end = (void *)(long)skb->data_end; - struct ethhdr *eth = data; - struct iphdr *ip; - struct udphdr *udp; - unsigned long long h_proto, p; - unsigned long long dport; - unsigned long long sport; - unsigned char buf[256]; - - if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) > data_end) - return 0; - - p = eth->h_proto; - if (ntohs(p) != ETH_P_IP) - return 0; - - //bpf_printk("ip: %d",ntohs(p)); - //bpf_printk("s: %d", skb->data_end); - bpf_skb_load_bytes(skb, 0, &buf, 41); - - ip = (struct iphdr*)(data + sizeof(struct ethhdr)); - - h_proto = ip->protocol; - // If not UDP packet - if (h_proto != 17) - return 0; - - // bpf_printk("proto: %d", h_proto); - //udp = (struct udphdr*)(data + sizeof(struct ethhdr) + sizeof(struct iphdr)); - - /*bpf_skb_load_bytes(skb, sizeof(struct ethhdr) + sizeof(struct iphdr), &udp, sizeof(struct udphdr)); - - // 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, eth, ip, udp, dport, sport);*/ - - return 0; -} -/*SEC("xdp") -int detect_dns(struct xdp_md *ctx){ - void *data_end = (void *)(long)ctx->data_end; - void *data = (void *)(long)ctx->data; - struct ethhdr *eth = data; - struct iphdr *ip; - struct udphdr *udp; - __u16 h_proto; - __u16 dport; - - if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) > data_end) - return XDP_DROP; - - ip = (struct iphdr*)(data + sizeof(struct ethhdr)); - udp = (struct udphdr*)(data + sizeof(struct ethhdr) + sizeof(struct iphdr)); - - h_proto = ip->protocol; - // If not UDP packet - if (h_proto != 17) - return XDP_PASS; - - // Check if DNS port - dport = udp->dest; - bpf_printk("Dport: %d", (dport)); - - return XDP_PASS; -}*/ -char LICENSE[] SEC("license") = "GPL"; diff --git a/src/dns-trace.ebpf.o b/src/dns-trace.ebpf.o index ea3c133390cd922d379065e14a88ae5fb1a64d37..bbbb9e0c5af03286b40f3a0430082d4b645484b7 100644 GIT binary patch literal 39712 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 z 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 Ym#EBluvI4F8QoBp<8!64XVw${7m=qE(*OVf diff --git a/src/skel.ebpf.h b/src/skel.ebpf.h deleted file mode 100644 index e69de29..0000000