Linux 上的经典 BPF:过滤器不起作用

Posted

技术标签:

【中文标题】Linux 上的经典 BPF:过滤器不起作用【英文标题】:classic BPF on Linux: filter does not work 【发布时间】:2017-01-25 04:51:32 【问题描述】:

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

下面是代码示例:

#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 \n" , iph->tot_len);
            if (recvfrom(s, bbuf, 500, 0, (struct sockaddr *) &sin, &re) < 0)
                printf("Recv failed\n");
            else
            
                printf("%x %x %x %x %x \n", bbuf[0], bbuf[1], bbuf[2], bbuf[3], bbuf[4] );
            
            if (recvfrom(s, bbuf, 500, 0, (struct sockaddr *) &sin, &re) < 0)
                printf("Recv failed\n");
            else
            
                printf("%x %x %x %x %x \n", bbuf[0], bbuf[1], bbuf[2], bbuf[3], bbuf[4] );
            
        
        break;
    

    return 0;

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

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

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

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

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

它仅适用于 tcpdump =(

【问题讨论】:

【参考方案1】:

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

在这种情况下,您在程序中的前两项检查没有被修改:

 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),我们也可以简单地摆脱这个检查。

所以整个过滤器将是:

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

【讨论】:

你太棒了!感谢您的帮助! @Grayscale 不客气!感谢您的投票和接受:-) 这个答案是正确的,但在另一种情况下 ebpf 似乎不起作用 - when packets were received after the socket's creation and before the call to setsockopt @NatanYellin 谢谢,写得非常好,我学到了一些东西!您可能还想将此作为对this other question 的回答,其中 OP 正在解决您所描述的问题。 假设以太网标头已经被剥离,是否可以让tcpdump 输出 BPF 字节码?或者,是手动调整字节码的唯一选择吗?

以上是关于Linux 上的经典 BPF:过滤器不起作用的主要内容,如果未能解决你的问题,请参考以下文章

为啥伪元素上的过滤器渐变在 IE8 中不起作用?

在 Swift 3 中过滤 UICollectionview 上的结果不起作用?

ng-repeat 上的 Angularjs OrderBy 不起作用

简单的 JFileChooser FileFilter 不起作用

多维动态数组,为啥不起作用?

UITableViewCell 样式字幕多行不起作用