|
Mini tutorial de manipulação de pacotes de rede com o libipqAutor: Ulysses Pereira de Almeida Netoulysses@mileniuminformatica.com.br 10/05/2006 Quando comecei a estudar sobre o assunto, achei poucos textos explicativos, e depois de aprender um pouco em cima de man pages e códigos de terceiros, resolvi escrever esse tutorial para quem possa achar útil. Mini tutorial de libIPQ ************************ Motivação Ao tentar aprender como poderia usar a ação NF_QUEUE do netfilter, achei poucas informações. Portanto a medida que eu aprendia (na verdade, ainda estou aprendendo), resolvi escrever sobre o assunto. A vantagem de usar a libipq, é que podemos criar regras para serem aplicadas em user space, ao invés de criar regras em kernel space. Bom, mas o que isso trás de vantagem? Com isso, você pode escrever seu próprio programa para manipular pacotes e aplicar regras de firewall, com a vantagem que não precisa se preocupar muito com o ambiente de desenvolvimento do kernel, pois você não irá mecher com ele. Você poderá criar um programa usando as glibc, biblioteca bastante conhecida por programadores, cujo a maioria das funções não possui equivalentes no ambiente de desenvolvimento do kernel. Netfilter Quando um pacote TCP/IP está viajando pelo kernel, ele passa por alguns "HOOKS" do netfilter, e nesses HOOKS, podemos aplicar algumas ações em cima do pacote, que são elas: * NF_ACCEPT Continua a viagem * NF_DROP Descarta o pacote * NF_STOLEN Rouba o pacote (não continua a viagem) * NF_QUEUE Manda pacote para uma fila, para ser capturada em user space * NF_REPEAT Faz com que o pacote seja processado novamente pelo HOOK Quando aplicamos o veredito NF_QUEUE, esse pacote é enviado para user space através de um socket. Assim, em user space, podemos capturar o pacote, e manipula-lo praticamente da mesma maneira que fazemos dentro do kernel. Podemos aplicar vereditos, marcar pacotes, inclusive trocar informações de um pacote. A grande vantagem é que isso pode ser feito em qualquer linguagem, basta abrir um socket para capturar esses pacotes. Para aplicar essa regras a determinados pacotes, podemos utilizar o iptables, depois de carregar o modulo ip_queue, como no exemplo a seguir: # modprobe ip_queue # iptables -p tcp -I INPUT -j QUEUE Assim, falamos para todos os pacotes com protocolo tcp, serem enviados para user space, através de um socket. Tome cuidado, pois se não existir nenhum programa para capturar esses pacotes, os pacotes são simplesmente descartados (NF_DROP), pelo netfilter. Portanto coloque seu programa para funcionar, antes de aplicar essa regra. LibIPQ Para nossa alegria, já foi desenvolvida uma biblioteca (netfilter core team), para facilitar essa comunicação entre kernel space e user space via socket. Com ela não precisamos entrar nos detalhes do socket, basta pedir os pacotes que estão sendo enviados para a fila. Descrição das principais funções da libipq, tirada das páginas de manual. * ipq_create_handle() Inicializa a biblioteca, retorna o manipulador do socket. Essa função sempre será chamada para criar o socket de comunicação entre user space e kernel space. * ipq_set_mode() Configura o modo para colocar em fila, podendo ser somente o metadata, ou metadata mais payload (pacotes inteiro) * ipq_read() Espera uma mensagem chegar de ip_queue, e envia ela para buffer. * ipq_message_type() Descobre o tipo de mensagem no buffer. * ipq_get_packet(); Recupera o pacote, do buffer lido por ipq_read() * ipq_set_verdict() Define o veredito para o pacote (NF_DROP ou NF_ACCEPT), sendo possível trocar o conteúdo do pacote. * ipq_destroy_handle() Destrói o manipulador, fechando o socket de comunicação. Como manipular os pacotes A função ipq_get_packet(), como vimos acima, nos retorna o pacote (com ou sem payload), para que possamos setar veredito ou manipular as informações do pacote. Essa função retorna um ponteiro para a estrutura descrita abaixo. typedef struct ipq_packet_msg { unsigned long packet_id; /* ID do pacote em fila */ unsigned long mark; /* Valor de "mark" do Netfilter (mangle) */ long timestamp_sec; /* Tempo de chegada do pacote (segundos) */ long timestamp_usec; /* Tempo de chegada do pacote (+useconds) */ unsigned int hook; /* HOOK do Netfilter, onde estava a regra para enviar o pacote para fila */ char indev_name[IFNAMSIZ]; /* Nome da interface de entrada */ char outdev_name[IFNAMSIZ]; /* Nome de interface de saída */ unsigned short hw_protocol; /* Protocolo de hardware (network order) */ unsigned short hw_type; /* Tipo de hardware */ unsigned char hw_addrlen; /* Tamanho do endereço de hardware */ unsigned char hw_addr[8]; /* Endereço de hardware */ size_t data_len; /* Tamanho dos dados do pacote */ unsigned char payload[0]; /* Dados do pacote (opcional) */ } ipq_packet_msg_t; Caso seja necessário trocar informações do pacote, será necessário pedir para que os dados do pacote (payload) venham para user space, sendo apontado pelo payload. Caso seja necessário alterar informações do pacote, é importante atualizar o checksum do pacote. O payload, aponta para um estrutura que é copia do skb->data. Que na verdade é uma cópia do pacote. "LibIPQ by example" Nada melhor do que um exemplo, para ver como as coisas funcionam. Esse exemplo é uma mistura do exemplo da página de manual da libipq, com o partes do intercept.c, provida pela equipe do netfilter (via cvs) e algumas alterações minhas. O código está todo comentado, para explicar o que faz cada função/estrutura. /* * This code is GPL. */ #include <sys/types.h> #include <limits.h> #include <net/if.h> #include <netinet/ip.h> #include <linux/netfilter_ipv4.h> #include <linux/tcp.h> #include <linux/netfilter.h> #include <libipq/libipq.h> #include <stdio.h> #define BUFSIZE 2048 #define DECIMAL_BYTE_VALUE 255 static char * print_bits (__u16 port) { char * str; int i; str = (char *)malloc(sizeof (char)*17); for (i=15; i>=0; i--) { str[i] = (char)((port&1) + 48); port >>= 1; } str[16] = '\0'; return str; } /* * Transforma um ip no formato numérico (32bits) em string com pontos. */ static char * __u32_to_str (__u32 ip) { unsigned int tmp = 0; int i; char *str_ip; str_ip = (char *)malloc (sizeof(char) * 16); str_ip[0] = '\0'; for (i=0; i<4; i++) { tmp = ip & DECIMAL_BYTE_VALUE; sprintf ((str_ip + strlen(str_ip)), "%d.", (unsigned int)tmp); ip >>= 8; } sprintf ((str_ip + (strlen(str_ip) - 1)), "%c", '\0'); //erase last dot str_ip[15] = '\0'; //just safe. return ((char *)str_ip); } static void die(struct ipq_handle *h) { ipq_perror("passer"); /* destrói o socket criado pela ipq_create_handle */ ipq_destroy_handle(h); exit(1); } int main(int argc, char **argv) { int status; unsigned char buf[BUFSIZE]; struct ipq_handle *h; unsigned char *payload; /* * Cria o sokect de comunicação, para receber * os pacotes do kernel space */ h = ipq_create_handle(0, PF_INET); if (!h) die(h); /* * Configura para receber o payload (pacote completo) */ status = ipq_set_mode(h, IPQ_COPY_PACKET, BUFSIZE); if (status < 0) die(h); do{ /* * Entra em estado de espera por pacotes enviados à fila. * Para cada pacote, faz uma cópia da informação, em * memória apontada por buf */ status = ipq_read(h, buf, BUFSIZE, 0); if (status < 0) die(h); /* * Verifica o tipo da mensagem em fila */ switch (ipq_message_type(buf)) { case NLMSG_ERROR: /* Opz, algo errado acorreu. */ fprintf(stderr, "Received error message %d\n", ipq_get_msgerr(buf)); break; case IPQM_PACKET: { /* * Recebemos um pacote. */ ipq_packet_msg_t *m = ipq_get_packet(buf); /* * Como pedimos o payload, vamos pegar o cabeçalho * ip do pacote (que está no início da estrutura) * e guardar o endereço dele em um ponteiro * específico para esse tipo de estrutura. */ struct iphdr *iph = ((struct iphdr *)m->payload); struct tcphdr *tcph; if (!iph) { /* * Opz, no ip! */ goto outch; } /* * Nesse caso, temos certeza que é um pacote TCP, pois * estamos enviando apenas TCP para fila. */ tcph = (struct tcphdr *)(m->payload + (iph->ihl << 2)); if (!tcph) goto outch; /* Opz, no tcp */ fprintf(stdout, "Accepting packets\n"); payload = m->payload; if (tcph) /* * Imprimindo informações coletadas. * ip origem, porta tcp origem, * ip destino, porta tcp destino. */ fprintf(stdout, "Packet src -> dst = %s:%d -> %s:%d\n", __u32_to_str ((__u32)(iph->saddr)), ntohs((__u16)(tcph->source)), __u32_to_str ((__u32)(iph->daddr)), ntohs((__u16)(tcph->dest))); else fprintf(stdout, "Packet src -> dst = %s:xx -> xx:xx\n", __u32_to_str ((__u32)(iph->saddr))); ok: /* * Aceitando o pacote, com isso ele volta ao kernel * space e continua sua jornada normalmente. */ status = ipq_set_verdict(h, m->packet_id, NF_ACCEPT, 0, NULL); if (status < 0) die(h); break; outch: /* * Apenas imprime uma mensagem de debug, caso algo * de errado. */ printf ("outch\n"); goto ok; } default: fprintf(stderr, "Unknown message type!\n"); break; } } while (1); /* loop infinito, enquanto nao der erro, leia pacotes */ /* finaliza a conexao */ ipq_destroy_handle(h); return 0; } Para compilar, pode ser usado o comando: $ gcc -o ipqteste ipqteste.c -lipq Para executá-lo é necessário estar como root. Esse programa simplesmente irá receber pacotes tcp/ip, e irá imprimir informações sobre o pacote na tela, liberando o pacote para continuar normalmente sua jornada. Apenas com a utilidade para conhecer o que pode ser feito com o libipq. Com esse exemplo, vemos que conseguimos grandes liberdades para manipulação de pacotes, mesmo sendo em user space. Mais informações sobre o que pode ser adquirido com o payload, é aconselhado que estude a estrutura sk_buff, definida nos códigos fontes do kernel (linux/skbuff.h) Considerações Nesse documento, principalmente no fonte exemplo (comentado), tentei esclarecer os pontos eu tive mais dificuldades. Espero que seja útil para alguém. Caso ainda reste dúvidas, sinta-se a vontade em me escrever, só não posso garantir que irei resolver sua dúvida! ;) Referências * Linux netfilter Hacking HOWTO * Linux netfilter devel mail list |
|
|
|
Desenvolvido por Aware |