Linux 上的经典 BPF:过滤器不起作用 [英] classic BPF on Linux: filter does not work

查看:37
本文介绍了Linux 上的经典 BPF:过滤器不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试通过将经典 BPF 附加到原始套接字来测试用于数据包过滤的经典 BPF.我想用源端口的第一个字节 == 8 来捕获 TCP 数据包(tcpdump 'tcp[1:1] = 0x50'),但我在套接字上看不到传入的数据包.没有过滤器我的代码工作正常.

I'm trying to test classic BPF for packet filtering by attaching it to raw socket. I want to catch TCP packets with first byte of source port == 8 (tcpdump 'tcp[1:1] = 0x50'), but I see no incoming packets on the socket. Without filter my code works OK.

代码示例如下:

#include<stdio.h> //for printf
#include<string.h> //memset
#include<sys/socket.h>    //for socket ofcourse
#include<stdlib.h> //for exit(0);
#include<errno.h> //For errno - the error number
#include<netinet/tcp.h>   //Provides declarations for tcp header
#include<netinet/ip.h>    //Provides declarations for ip header
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/filter.h>


#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof((arr)[0])
/* 
   96 bit (12 bytes) pseudo header needed for tcp header checksum calculation 
*/
struct pseudo_header
{
    u_int32_t source_address;
    u_int32_t dest_address;
    u_int8_t placeholder;
    u_int8_t protocol;
    u_int16_t tcp_length;
};

/*
  Generic checksum calculation function
*/
unsigned short csum(unsigned short *ptr,int nbytes) 
{
    register long sum;
    unsigned short oddbyte;
    register short answer;

    sum=0;
    while(nbytes>1) {
        sum+=*ptr++;
        nbytes-=2;
    }
    if(nbytes==1) {
        oddbyte=0;
        *((u_char*)&oddbyte)=*(u_char*)ptr;
        sum+=oddbyte;
    }

    sum = (sum>>16)+(sum & 0xffff);
    sum = sum + (sum>>16);
    answer=(short)~sum;

    return(answer);
}

int main (void)
{
    struct sock_filter code[] = {

        { 0x28,  0,  0, 0x0000000c },
        { 0x15,  0,  9, 0x00000800 },
        { 0x30,  0,  0, 0x00000017 },
        { 0x15,  0,  7, 0x00000006 },
        { 0x28,  0,  0, 0x00000014 },
        { 0x45,  4,  0, 0x00001fff },
        { 0xb1,  0,  0, 0x0000000e },
        { 0x50,  0,  0, 0x0000000f },
        { 0x15,  0,  2, 0x00000050 },
        { 0x06,  0,  0, 0xffffffff },
        { 0x06,  0,  0, 0xffffffff },
        { 0x06,  0,  0, 0000000000 },
    };

    struct sock_fprog bpf;
//    bpf.len = ARRAY_SIZE(code);
    bpf.len = 12;
    bpf.filter = code;
//Create a raw socke
    int s = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);
    if(s == -1)
    {
        //socket creation failed, may be because of non-root privileges
        perror("Failed to create socket");
        exit(1);
    }

    setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
    perror("setsockopt");
//Datagram to represent the packet
    char datagram[4096] , source_ip[32] , *data , *pseudogram;

//zero out the packet buffer
    memset (datagram, 0, 4096);

//IP header
    struct iphdr *iph = (struct iphdr *) datagram;

//TCP header
    struct tcphdr *tcph = (struct tcphdr *) (datagram + sizeof (struct ip));
    struct sockaddr_in sin;
    struct pseudo_header psh;

//Data part
    data = datagram + sizeof(struct iphdr) + sizeof(struct tcphdr);
    strcpy(data , "ABCDEFGHIJKLMNOPQRSTUVWXYZ");

//some address resolution
    strcpy(source_ip , "127.0.0.1");
    sin.sin_family = AF_INET;
    sin.sin_port = htons(80);
    sin.sin_addr.s_addr = inet_addr ("127.0.0.1");

    bind(s, (struct sockaddr *)&sin, sizeof(sin));
    perror("bind");

//Fill in the IP Header
    iph->ihl = 5;
    iph->version = 4;
    iph->tos = 0;
    iph->tot_len = sizeof (struct iphdr) + sizeof (struct tcphdr) + strlen(data);
    iph->id = htonl (54321); //Id of this packet
    iph->frag_off = 0;
    iph->ttl = 255;
    iph->protocol = IPPROTO_TCP;
    iph->check = 0;      //Set to 0 before calculating checksum
    iph->saddr = inet_addr ( source_ip );    //Spoof the source ip address
    iph->daddr = sin.sin_addr.s_addr;

//Ip checksum
    iph->check = csum ((unsigned short *) datagram, iph->tot_len);

//TCP Header
    tcph->source = htons (1234);
    tcph->dest = htons (80);
    tcph->seq = 0;
    tcph->ack_seq = 0;
    tcph->doff = 5;  //tcp header size
    tcph->fin=0;
    tcph->syn=1;
    tcph->rst=0;
    tcph->psh=0;
    tcph->ack=0;
    tcph->urg=0;
    tcph->window = htons (5840); /* maximum allowed window size */
    tcph->check = 0; //leave checksum 0 now, filled later by pseudo header
    tcph->urg_ptr = 0;

//Now the TCP checksum
    psh.source_address = inet_addr( source_ip );
    psh.dest_address = sin.sin_addr.s_addr;
    psh.placeholder = 0;
    psh.protocol = IPPROTO_TCP;
    psh.tcp_length = htons(sizeof(struct tcphdr) + strlen(data) );

    int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr) + strlen(data);
    pseudogram = malloc(psize);

    memcpy(pseudogram , (char*) &psh , sizeof (struct pseudo_header));
    memcpy(pseudogram + sizeof(struct pseudo_header) , tcph , sizeof(struct tcphdr) + strlen(data));

    tcph->check = csum( (unsigned short*) pseudogram , psize);

//IP_HDRINCL to tell the kernel that headers are included in the packet
    int one = 1;
    const int *val = &one;

    if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, val, sizeof (one)) < 0)
    {
        perror("Error setting IP_HDRINCL");
        exit(0);
    }

//loop if you want to flood :)
    while (1)
    {
        //Send the packet
        if (sendto (s, datagram, iph->tot_len ,  0, (struct sockaddr *) &sin, sizeof (sin)) < 0)
        {
            perror("sendto failed");
        }
        //Data send successfully
        else
        {
            char bbuf[500];
            int re = 0;
            printf ("Packet Send. Length : %d 
" , iph->tot_len);
            if (recvfrom(s, bbuf, 500, 0, (struct sockaddr *) &sin, &re) < 0)
                printf("Recv failed
");
            else
            {
                printf("%x %x %x %x %x 
", bbuf[0], bbuf[1], bbuf[2], bbuf[3], bbuf[4] );
            }
            if (recvfrom(s, bbuf, 500, 0, (struct sockaddr *) &sin, &re) < 0)
                printf("Recv failed
");
            else
            {
                printf("%x %x %x %x %x 
", bbuf[0], bbuf[1], bbuf[2], bbuf[3], bbuf[4] );
            }
        }
        break;
    }

    return 0;
}

过滤器的代码由 bpf_asm -c 从以下生成:

The code for filter was generated by bpf_asm -c from the following:

ldh      [12]
jneq      #0x800, drop
ldb      [23]
jneq      #0x6, drop
ldh      [20]
jset     #0x1fff, good
ldxb     4*([14]&0xf)
ldb      [x + 15]
jneq      #0x50, drop
ret #-1
good: ret      #-1
drop: ret      #0

我还尝试了以下说明:我还尝试了以下方法:

I also tried the following instructions: I also tried the following:

tcpdump 'ether[35:1] = 0x50'

ldb      [35]
jneq      #0x50, drop
ret      #-1
drop: ret      #0

它仅适用于 tcpdump =(

it works for tcpdump only =(

推荐答案

就你的程序而言,似乎 BPF 过滤器直接应用于以太网负载(从 IP 标头开始)而不是整个以太网帧.

In the case of your program, it seems that the BPF filter is applied directly on the Ethernet payload (starting with the IP headers) instead of the whole Ethernet frame.

在这种情况下,您在程序中的前两个检查未进行调整:

In this case, the first two checks you have in your program are not adapted:

{ 0x28,  0,  0, 0x0000000c }, // Load ethertype byte
{ 0x15,  0,  9, 0x00000800 }, // Goto drop if it is not == 0x800 (IPv4)
{ 0x30,  0,  0, 0x00000017 }, // Load IP protocole number
{ 0x15,  0,  7, 0x00000006 }, // Goto drop if it is not == 0x6 (TCP)

相反,我们应该:

  • 跳过以太网类型检查(我们已经知道我们有 IP).
  • 更改 IP 协议编号的偏移量.

过滤器的开头改为:

{ 0x30,  0,  0, 0x00000009 }, // …09 Instead of …17: we start from beginning of IP header
{ 0x15,  0,  7, 0x00000006 },

事实上,由于您创建了一个只会接收 TCP 数据包的套接字 (int s = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);,另请参见 man 7 raw),我们也可以简单地去掉这个检查.

In fact, since you create a socket that will receive only TCP packets (int s = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);, see also man 7 raw), we can also simply get rid of this check as well.

所以整个过滤器将是:

struct sock_filter code[] = {

    { 0x30,  0,  0, 0x00000009 },
    { 0x15,  0,  7, 0x00000006 },
    { 0x28,  0,  0, 0x00000014 },
    { 0x45,  4,  0, 0x00001fff },
    { 0xb1,  0,  0, 0x0000000e },
    { 0x50,  0,  0, 0x00000013 },
    { 0x15,  0,  2, 0x00000050 },
    { 0x06,  0,  0, 0xffffffff },
    { 0x06,  0,  0, 0xffffffff },
    { 0x06,  0,  0, 0000000000 },

};

或者更简单:

struct sock_filter code[] = {

    { 0x28,  0,  0, 0x00000014 },
    { 0x45,  4,  0, 0x00001fff },
    { 0xb1,  0,  0, 0x0000000e },
    { 0x50,  0,  0, 0x00000013 },
    { 0x15,  0,  2, 0x00000050 },
    { 0x06,  0,  0, 0xffffffff },
    { 0x06,  0,  0, 0xffffffff },
    { 0x06,  0,  0, 0000000000 },

};

附注:

#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof((arr)[0]))
                                                     ^ ending parenthesis missing

这篇关于Linux 上的经典 BPF:过滤器不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆