classic BPF on Linux: filter does not work

3

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.

Here is the code example:

#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;
}

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

it works for tcpdump only =(

c
linux
sockets
networking
bpf
asked on Stack Overflow Sep 16, 2016 by Grayscale • edited Sep 16, 2016 by Grayscale

1 Answer

8

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)

Instead, we should:

  • Skip ethertype check (we already know we have IP anyway).
  • Change offset for IP protocol number.

The beginning of the filter becomes instead:

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

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.

So the whole filter would be:

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 },

};

Or more simply:

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 },

};

Side note:

#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof((arr)[0]))
                                                     ^ ending parenthesis missing
answered on Stack Overflow Sep 17, 2016 by Qeole • edited Sep 28, 2016 by Qeole

User contributions licensed under CC BY-SA 3.0