> ## Content Index
> Fetch the complete content index at: https://yokai.hakaisecurity.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# [PT-BR] DNS Query Detection via Direct Packet Inspection
- URL: https://yokai.hakaisecurity.io/pt-br-dns-query-detection-via-direct-packet-inspection/
- Published: 2026-02-27T20:41:30.000Z
- Updated: 2026-09-08T16:39:51.000Z
- Author: Carlos Guilherme
- Tags: Research Blog, #wp, pt-br, #pair-dns-query

Com o conhecimento sobre o funcionamento de sistemas operacionais, é sabido que os sistemas comuns são compostos de um kernel (parte central do sistema que realiza a comunicação com o hardware, gerenciamento de memória, separação dos níveis de privilégios, interfaces de comunicação e outros) e o espaço de usuário, contendo um conjunto de utilitários (essa é uma descrição abstrata, portanto não significa que todos os sistemas operacionais funcionam dessa maneira). O artigo arbodará a utilização do **eBPF** (extended Beckerley Packet Filter), em específico **XDP**, para o processamento de pacotes no kernel Linux, facilitando a contagem de requisições DNS em userland. Será demonstrado o entendimento dessa tecnologia e como utilizá-la para obter dados de consultas **DNS**. O Linux representa um dos maiores projetos opensource do planeta, com mais de 40 milhões de linhas de código. Ao decorrer do tempo, foram desenvolvidas tecnologias para o kernel Linux que ajudaram no processo de expansão de suas funcionalidades, sem a necessidade de realizar modificação do código kernel propriamente dito e compilação, demandando esforço, tempo e conhecimento técnico do ambiente que está sendo modificado.

Com os problemas destacados, surgiu a possibilidade de gerenciar pacotes de rede e expandir as funcionalidades já existentes em tempo de execução. O eBPF é uma atualização do BPF (Beckerley Packet Filter), ou também conhecido como *classic* BPF, teve a passagem para o modelo extendido em **2014** no kernel Linux.

A primeira versão (*classic*), era amplamente utilizada para o processamento de pacotes e filtragem. Na versão extendida, houve um avanço nas ações que podem ser realizadas com o eBPF, além de operações de rede, como *tracing*, *hook* de funções, interceptação de chamadas de sistema e também ações mais generalistas, como implementações relacionadas à segurança, observabilidade, *load balancing*, *debugging*, *firewalling*, entre outros.

Para ter uma compreensão ampla do artigo, é importante entender o conceito básico sobre o *Express Data Path* (XDP). O XDP é um tecnologia do eBPF que possibilita o gerenciamento de pacotes de forma expressa atrelado diretamente à interface de rede, ou seja, podemos dizer que ele faz o processamento dos pacotes mais cedo no kernel (diferente do fluxo de rede comum), o que justifica prefixo "**express**".

No conteúdo que será abordado neste *post*, para cada pacote que sai do sistema, será implementada uma lógica para estruturar e classificar o pacote, sendo possível realizar diferentes ações dependendo do protocolo trafegado. O foco será o protocolo UDP, no qual cada pacote é coletado para possibilitar a criação de um histórico das consultas DNS trafegadas pelo, bem como a quantificação dessas requisições, identificando comportamentos fora do comum. A ideia central é ter uma observabilidade de forma leve e simplificada. Vale ressaltar que por se tratar de um programa para fins didáticos, questões de eficiência não serão levadas tanto em consideração, o conteúdo serve como fonte de ideias para a criação de projetos.

### Packet parsing

A codificação do projeto foi realizada com a biblioteca [libbpf](https://github.com/libbpf/libbpf?ref=yokai.hakaisecurity.io), pois ela implementa interfaces para utilizar o eBPF, tanto em *user space* quanto em *kernel space*.

Uma maneira de se comunicar com as funções eBPF no kernel é por meio da *system call* bpf, permitindo a criação de mapas, localizar elementos, atualizar elementos, fazer operações de fixação, atrelar um programa BPF, obter informações, entre outras operações .

![](https://hakaisecurity.io/wp-content/uploads/2026/02/2025-11-30_19-21.png)

**https://man7.org/linux/man-pages/man2/bpf.2.html*

Porém um ponto importante é que, realizar essas chamadas puramente pode apresentar uma barreira de complexidade na codificação. Portanto, como a biblioteca abstrai esse nível de complexidade, basta ter conhecimento das funções e de como utilizá-las durante o desenvolvimento. Isso é válido para desenvolver programas eBPF em C, Python, go e outras linguagens de programação.

No geral, programas eBPF são considerados seguros. Esse comportamento se dá devido à existência do verificador, onde ocorre a checagem das instruções e a verificação de todos os caminhos de execução do programa.

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/2025-11-24_07-03.png)

Para realizar a captura, é importante seguir um fluxo de interpretação dos pacotes que saem do sistema. Em cada protocolo, será preciso realizar sua verificação e *parsing* utilizando uma estrutura adequada ao protocolo. O entendimento do programa enquanto o desenvolvimento está sendo realizado pode ser simplificado com a exibição de cada variável das estruturas que foram atreladas. É interessante que se tenha certeza de que uma variável é realmente o valor esperado após sequências de *parsing*.

No programa criado, a primeira parte do pacote bruto é interpretada em uma estrutura *ethernet* (**ethhdr**). Note que, sempre quando ocorre o *parsing* do pacote, é preciso estabelecer uma verificação para aceito pelo *verifier*.

```c
if(data + sizeof(struct ethhdr) < data_end){
        struct ethhdr *eth = data;

```

Outra estrutura utilizada é a **iphdr**, possibilitando que o protocolo IP seja manuseado.

```c
if(data + sizeof(struct ethhdr) + sizeof(struct iphdr) < data_end){
    struct iphdr *iph   = data + sizeof(struct ethhdr);
}
```

Para exemplificar as ações descritas de como cada protocolo do pacote pode ser inspecionado, será demonstrado um ping para o domínio **google.com**.

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/2025-10-30_07-59.png)

Veja que o parâmetro da função para exibir o endereço em formato hexadecimal é uma estrutura **iphdr**.

```c
void show_addresses(struct iphdr *i){
        bpf_printk("Source - %x", i->saddr);
}
```

No subsistema tracing do Linux, o retorno da função retorno da função responsável pela exibição do endereço de origem foi consultado. Ao inspecionar o *trace*, são visíveis as mensagens as mensagens emitidas pelo programa eBPF/XDP.

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/2025-10-30_08-00.png)

A exibição foi feita em hexadecimal apenas para fins demonstrativos. O endereço IP é lido a partir da ordem inversa dos bytes. Cada byte (dois caracteres no formato hexadecimal) representa um octeto do endereço IP. Segundo pesquisas realizadas, a discrepância da interpretação de pacotes em determinadas camadas é o resultado das tecnologias terem sido criadas separadamente, ou seja, primordialmente não foram feitas para serem utilizadas conforme a combinação atual. Com isso, determinada camada pode ter os dados organizados em *little-endian* e outras em *big-endian*.

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/2025-10-30_08-05.png)

Até o momento da interpretação do pacote, ocorreu a leitura do protocolo *Ethernet* e IP. Em seguida, a camada de transporte será interpretada.

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/05fd4fef7c039e4f2ff6ff83c5a4e231.png)

### Payload access

A interpretação do protocolo de transporte é essencial para funcionar o programa de inspeção, pois ele definirá qual será o destino final do pacote. O objetivo central é obter todas as requisições DNS que estão sendo trafegadas no sistema e o protocolo DNS usa majoritariamente **UDP** para realizar as consultas por questões de eficiência. O seguinte diagrama foi extraído da [RFC](https://www.rfc-editor.org/rfc/rfc8085?ref=yokai.hakaisecurity.io) do UDP.

```text
0      7 8     15 16    23 24    31
+--------+--------+--------+--------+
|          source address           |
+--------+--------+--------+--------+
|        destination address        |
+--------+--------+--------+--------+
|  zero  |protocol|   UDP length    |
+--------+--------+--------+--------+
```

A interpretação do conteúdo UDP ocorre com a utilização da estrutura **udph**, permitindo que os valores do diagrama anterior sejam acessados.

```c
struct udphdr *udph = data + sizeof(struct ethhdr) + sizeof(struct iphdr);

```

Veja que, com a exibição do tamanho dos pacotes UDP, os valores variam conforme a resolução dos domínios com tamanhos diferentes, indicando que o processo de parsing está correto.

```bash
dig A google.com
```

![](https://hakaisecurity.io/wp-content/uploads/2026/02/2025-12-17_22-31.png)

```bash
dig A BBBBBBBBBBBBBBBB.google.com
```

![](https://hakaisecurity.io/wp-content/uploads/2026/02/2025-12-17_22-32.png)

Com a leitura dos protocolos do pacote que está sendo interceptado, resta gerenciar o ***payload*** proprimamente dito. Nele, será possível inspecionar os dados úteis transportados pelo pacote UDP.

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/f8f789cb5559fd0ce6eea90a80a06bdc.png)

Segundo o [RFC768](https://www.rfc-editor.org/rfc/rfc768?ref=yokai.hakaisecurity.io), o datagrama UDP é transportado em um único pacote IP, possuindo um limite de *payload* de 64507 bytes para IPv4 e 65527 para IPv6.

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/hbt6znuaoaasfoq.png)

Então, para acessar o **payload** do pacote, basta avançar no dado bruto somando o tamanho das estruturas interpretadas anteriormente durante o *parsing*.

```c
if(data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) < data_end){
        if(udph->len >= MAX_UDP_SIZE){
            udph->len = MAX_UDP_SIZE;
        }
        
        udata->len = udph->len;
        
        char *payload = data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr);
```

Devido às verificações que o eBPF *verifier* faz, o *payload* será enviado para o *user space* utilizando *ring buffers*. Os *ring buffers* são estruturas de memória utilizadas para armazenar dados de forma cicular, onde conceitualmente os dados são consumidos a partir do *head* do *buffer* e novos valores são inseridos na extremidade oposta (tail), caracterizando uma estrutura de dados FIFO (first-in, first-out). O *buffer* é considerado como vazio quando ocorre o encontro do *head* e *tail*. Essa estrutura é implementada através dos eBPF *maps*, que são mecanismos para realizar a comunicação entre programas eBPF com o *user space* (ou programas em *kernel space*).

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/image-28.png)

Uma questão que pode ter surgido até o momento é: "**Como eu posso consumir esse buffer?**". É necessário que o *buffer* seja exposto. Sua identificação se dá pelo nome da estrutura que foi declarada no código eBPF, nesse caso "**rb**". O *pinning* é um atributo configurável na estrutura que, quando declarado, realiza o *pinning* do *ring buffer* no sistema de arquivos, tornando o *map* visível.

```c
struct {
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 256 * 1024);
    __uint(pinning, LIBBPF_PIN_BY_NAME);
} rb SEC(".maps");

```

Quando o programa eBPF for carregado, um *map* com o nome do *ring buffer* será criado no diretório "**/sys/fs/bpf/**". Outra maneira de listar os *maps* eBPF no sistema é utilizar a ferramenta **ebpftool** com o comando "**map list**".

```bash
bpftool map list
```

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/2026-02-04_12-08.png)

O trecho de código abaixo demonstra a reserva do *ring buffer* no kernel, utilizando a estrutura "**`udp_data`**", e, posteriormente, o envio dos dados para o *buffer*.

```c
struct udp_data *udata = bpf_ringbuf_reserve(&rb, sizeof(struct udp_data), 0);

__builtin_memcpy(udata->data, "INSERTEDDATA\x00",13);
        
bpf_ringbuf_submit(udata, 0);

```

Após a compilação, o programa pode ser carregado com o **bpftool** e anexação a uma interface de rede.

```bash
bpftool prog load exfiltration.bpf.o /sys/fs/bpf/name
bpftool net attach xdp pinned /sys/fs/bpf/name dev eth0
```

### User space packet analysis

Com a programação do *ring buffer* no programa eBPF/XDP uma opção para consumir dados é utilizando funções de gerenciamento do *ring buffer* feitas para o *user space*.

Semelhante à codificação feita no programa eBPF, para o programa responsável pela leitura dos dados no *user space*, a biblioteca *libbpf* também foi utilizada, possibilitando a abertura de um *handle* para o *ring buffer*. Em conjunto, é realizado o consumo do *buffer*, repassando todos os eventos para uma função responsável por ler o pacote e interpretar os campos que armazenam os dados da consulta DNS. Note que, na chamada da função "**ring\_buffer\_\_new**", uma função de *callback* para gerenciar os eventos do *buffer* é inserida como argumento.

```c
int handle_event(void *ctx, void *data, unsigned long size){
    printf("Received %d bytes\n", (int)size);

    for(int i=0; i < 150; i++){
        printf("%c - ", *((char *)data+i));
        if(i != 0 && i % 8 == 0){
                puts("\n");
        }
    }
    printf("\n");
    return 0;
}

map_fd = bpf_obj_get("/sys/fs/bpf/rb");
if (map_fd < 0) {
    printf("Invalid ring buffer");
    return 1;
}

struct ring_buffer *rb = ring_buffer__new(map_fd, handle_event, NULL, NULL);
    if (!rb) {
        printf("Failed to acquire ring buffer\n");
        return 1;
    }

while (!quit) {
    ring_buffer__poll(rb, 100);
}

ring_buffer__free(rb);
```

Com os dados disponíveis para consumo, na leitura direta dos dados recebidos via *ring buffer* no *user space*, é observável a presença de valores que representam uma consulta DNS.

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/image-29.png)

A leitura livre do protocolo UDP não é o suficiente para separar as consultas DNS de outros pacotes trafegados pelas aplicações no sistema. É necessário ajustar o gerenciamento para que apenas consultas DNS sejam coletadas. Uma abordagem é limitar a captura para à porta 53.

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/image-30.png)

Captura livre dos pacotes

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/image-31.png)

Captura com filtro de porta 53

Como a captura está acontecendo na interface de rede à qual o eBPF/XDP foi atrelado, toda consulta DNS dos programas em execução no sistema operacional que utilizarem essa interface serão coletadas.

![](https://hakaisecurity.io/wp-content/uploads/2026/02/image-32.png)

Como os dados foram armazenados em um banco de dados, a inspeção dos eventos pode ser efetuada por meio de consultas simples que permitem destacar um domínio específico, listar todos os registros ou obter a quantidade de vezes que determinado domínio foi consultado. Com opções de data e hora disponíveis, é possível trabalhar com análises em intervalos de tempo.

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/image-33.png)

Para trabalhos futuros sobre o tratamento automático dos dados coletados e de forma complexa, é preciso pensar que não é o suficiente realizar a contagem de consultas DNS em determinados espaços de tempo, porque um atacante pode realizar a extração das informações do ambiente com poucos *bytes* em períodos prolongados de dias.

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/image-34.png)

O tamanho do subdomínio da consulta DNS também não é o suficiente para destacar uma exfiltração, pois a extração pode ocorrer por um subdomínio extenso (vários carácteres no subdomínio) ou apenas de byte em byte, por exemplo "**41.domain.com**", "**42.domain.com**". Sabendo dessas nuâncias, é interessante que ocorra um trabalho de análise dos dados para que seja definido se o domínio analisado é confiável, conhecido e se o comportamento foge do padrão.

Assume-se que uma aplicação comum não contenha diversos subdomínos com alta randomização em seus valores, sem um sentido completo. Caso um comportamento como esse seja identificado, é possível que algum dado esteja sendo extraído do sistema. A implementação do programa foi realizada em um computador de uso comum, mas a abordagem pode ser aplicada para servidores e appliances que oferecem suporte para eBPF.

![](https://yokai.hakaisecurity.io/content/images/hakaisecurity-io/wp-content/uploads/2026/02/image-35.png)

A abordagem utilizando o protocolo UDP para captura de domínios é uma ação demonstrativa, mas a codificação apresentada pode ser utilizada para outros protocolos ou detecções. Todo o contexto descrito no *post* tem como objetivo demonstrar como, com uma solução simples, é possível ter visibilidade das atividades que acontecem no sistema operacional, permitindo metrificar tempo, quantidade e aleatoriedade de domínios/subdomínios. O exemplo demonstra que, se um sistema for comprometido e as interações com servidores externos ocorrerem via protocolo DNS, uma investigação mais precisa poderá ser efetuada.

**xdp.bpf.c**

```c
#include <linux/bpf.h>
#include <linux/ip.h>
#include <linux/if_ether.h>
#include <linux/string.h>
#include <linux/udp.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>

#define PROTO_ICMP 1
#define PROTO_UDP  17
#define PROTO_TCP  6
#define PROTO_RDP  27
#define PROTO_IPV6 41

#define MAX_UDP_SIZE 65527

struct udp_data {
int len;
char data[MAX_UDP_SIZE];
};

struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024);
__uint(pinning, LIBBPF_PIN_BY_NAME);
} rb SEC(".maps");

int mac_confusion(char *mac, char *newmac){
char old_mac[ETH_ALEN];

if(!mac){
return -1;
}

for(int i=0; i < ETH_ALEN; i++){
old_mac[i] = mac[i];
mac[i] = newmac[i];
}
return 0;
}

int parse_udp(void *data, void *data_end){
struct udphdr *udph = data + sizeof(struct ethhdr) + sizeof(struct iphdr);

if ((void *)(udph+1) > data_end){
    return XDP_PASS;
}

char port = bpf_ntohs(udph->source);
if(port != 53){
                return XDP_PASS;
        };

bpf_printk("UDP packet port %d", port);
        struct udp_data *udata = bpf_ringbuf_reserve(&rb, sizeof(struct udp_data), 0);

       if(!udata){
                return XDP_PASS;
        }

if(data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) < data_end){
if(udph->len >= MAX_UDP_SIZE){
udph->len = MAX_UDP_SIZE;
}
udata->len = udph->len;

char *payload = data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr);
if((void *)payload < data_end){
bpf_probe_read_kernel(udata->data, udph->len, payload);
}
}
bpf_ringbuf_submit(udata, 0);
return 0;

}

void parse_tcp(void *x){
}

void parse_icmp(void *x){
}

int prepare_packet(struct xdp_md *ctx){

void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;

if(data + sizeof(struct ethhdr) < data_end){
struct ethhdr *eth = data;
if(data + sizeof(struct ethhdr) + sizeof(struct iphdr) < data_end){
struct iphdr *iph   = data + sizeof(struct ethhdr);
unsigned char proto = iph->protocol;
int iph_len         = iph->ihl * 4;

if(iph_len > sizeof(struct iphdr)){
return XDP_PASS;
}

switch(proto){
case PROTO_ICMP:
parse_icmp(data);
break;
case PROTO_UDP:
return parse_udp(data, data_end);
case PROTO_TCP:
parse_tcp(data);
break;
default:
break;
}
       }
}
return 0;
}
SEC("xdp")
int exfiltration(struct xdp_md *ctx){

int ret = prepare_packet(ctx);
if(ret != 0){
return XDP_PASS;
}

return XDP_PASS;
}
char LICENSE[] SEC("license") = "Dual BSD/GPL";

```

**user\_code.c**

```c
#include <stdio.h>
#include <signal.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <sqlite3.h>
#include <stdlib.h>
#include <time.h>

sqlite3 *DB;

struct dns_header {
    short id;
    short flags;
    short qdcount;
    short ancount;
    short nscount;
    short arcount;
};

int parse_qname(char *data, char *out, int out_len) {
    int i = 4, j = 0;

    /*
    for(int i =0; i < 150; i++){
    printf("%c - ", *((char *)data+i));
    if(i != 0 && i % 8 == 0){
        puts("\n");
    }
    }*/

    while (data[i] != 0) {
        char len = data[i++];
        if (j + len + 1 >= out_len){
return -1;
}

        for (int k = 0; k < len; k++) {
            out[j++] = data[i++];
        }
        out[j++] = '.';
    }

    return i + 1;     
}

int evt = 0;

void handle(int sig) {
    evt = 1;
}

static int handle_event(void *ctx, void *data, size_t size) {
    time_t currTime;
    void *ptr = data + sizeof(struct dns_header);
    char domain[150];
    memset(domain, 0, sizeof(domain));
    int qname_len = parse_qname(ptr, domain, sizeof(domain));
    time(&currTime);

    if(qname_len < 0){
    printf("Invalid packet!\n");
    return 0;
    }
    //printf("Domain: %s %ld\n", domain, (long)currTime);
    printf("Domain: %s\n", domain);
    domain[qname_len - 6] = '\0'; // adjust with the parse alignment

    char query[230];
    sprintf(query, "INSERT INTO ddata (domain,time) values ('%s','%ld');",domain, currTime);
    sqlite3_exec(DB, query, NULL, NULL, NULL);

    return 0;
}

int main() {
    int err, map_fd;
    err = sqlite3_open("domaindata.db", &DB);

    if(err){
    printf("Error opening the database!\n");
    exit(1);
    }else{
    printf("Database opened.\n");
    }

    signal(SIGINT, handle);
    map_fd = bpf_obj_get("/sys/fs/bpf/rb");
    if (map_fd < 0) {
    printf("Invalid ring buffer");
        return 1;
    }

    struct ring_buffer *rb = ring_buffer__new(map_fd, handle_event, NULL, NULL);
    if (!rb) {
        printf("Failed to acquire ring buffer\n");
        return 1;
    }

    printf("To quit press Ctrl+C\n");
    while (!evt) {
        ring_buffer__poll(rb, 100);
    }

    ring_buffer__free(rb);
    return 0;
}

```

**Compilação**

```bash
clang -target bpf -g -O2 -c xdp.bpf.c -o xdp.bpf.o
clang user_code.c -lbpf -o user_space
```

**Referências**

[https://docs.ebpf.io/ebpf-library/libbpf](https://docs.ebpf.io/ebpf-library/libbpf?ref=yokai.hakaisecurity.io)

[https://ebpf.hamza-megahed.com/docs/chapter2/2-maps](https://ebpf.hamza-megahed.com/docs/chapter2/2-maps?ref=yokai.hakaisecurity.io)

[https://www.linuxtoday.com/blog/mythtv-36-0-open-source-media-center-is-out-now-with-support-for-ffmpeg-8](https://www.linuxtoday.com/blog/mythtv-36-0-open-source-media-center-is-out-now-with-support-for-ffmpeg-8?ref=yokai.hakaisecurity.io)

[https://www.rfc-editor.org/rfc/rfc768](https://www.rfc-editor.org/rfc/rfc768?ref=yokai.hakaisecurity.io)

[https://mostlynerdless.de/blog/2024/03/12/hello-ebpf-ring-buffers-in-libbpf-6/](https://mostlynerdless.de/blog/2024/03/12/hello-ebpf-ring-buffers-in-libbpf-6/?ref=yokai.hakaisecurity.io)

[https://docs.ebpf.io/ebpf-library/libbpf/userspace/ring\_buffer\_\_consume\_n](https://docs.ebpf.io/ebpf-library/libbpf/userspace/ring%5Fbuffer%5F%5Fconsume%5Fn?ref=yokai.hakaisecurity.io)

[https://www.cloudns.net/blog/dns-use-udp](https://www.cloudns.net/blog/dns-use-udp?ref=yokai.hakaisecurity.io)