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:过滤器不起作用的主要内容,如果未能解决你的问题,请参考以下文章
在 Swift 3 中过滤 UICollectionview 上的结果不起作用?
ng-repeat 上的 Angularjs OrderBy 不起作用