Skip to content

Commit

Permalink
http filter example
Browse files Browse the repository at this point in the history
  • Loading branch information
mbertrone committed Feb 5, 2016
1 parent 3ffffa7 commit b095058
Show file tree
Hide file tree
Showing 7 changed files with 672 additions and 0 deletions.
41 changes: 41 additions & 0 deletions examples/networking/http_filter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#Simple HTTP Filter: Project purpose

Write an eBPF application that parses HTTP packets and extracts (and prints on screen) the URL contained in the GET/POST request.

[https://github.com/netgroup-polito/ebpf-test](https://github.com/netgroup-polito/ebpf-test)

#Usage Example

```Shell
$ sudo python http-parse-v2.py
GET /pipermail/iovisor-dev/ HTTP/1.1
HTTP/1.1 200 OK
GET /favicon.ico HTTP/1.1
HTTP/1.1 404 Not Found
GET /pipermail/iovisor-dev/2016-January/thread.html HTTP/1.1
HTTP/1.1 200 OK
GET /pipermail/iovisor-dev/2016-January/000046.html HTTP/1.1
HTTP/1.1 200 OK
```

#Implementation using BCC

eBPF socket filter.<br />
Filters IP and TCP packets, containing "HTTP", "GET", "POST" in payload and all subsequent packets belonging to the same session, having the same (ip_src,ip_dst,port_src,port_dst).<br />
Program is loaded as PROG_TYPE_SOCKET_FILTER and attached to a socket, bind to eth0. <br />
Matching packets are forwarded to user space, others dropped by the filter.<br />
<br />
Python script reads filtered raw packets from the socket, if necessary reassembles packets belonging to the same session, and prints on stdout the first line of the HTTP GET/POST request. <br />

# Usage

Require:
- BPF Compiler Collection [BCC](https://github.com/iovisor/bcc)
- Follow [INSTALL](https://github.com/iovisor/bcc/blob/master/INSTALL.md) guide

# To run:

```Shell
$ sudo python http-parse.py
$ sudo python http-parse-v2.py
```
Binary file not shown.
Binary file not shown.
150 changes: 150 additions & 0 deletions examples/networking/http_filter/http-parse-v2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>

#define IP_TCP 6
#define ETH_HLEN 14

struct Key {
u32 src_ip; //source ip
u32 dst_ip; //destination ip
unsigned short src_port; //source port
unsigned short dst_port; //destination port
};

struct Leaf {
int timestamp; //timestamp in ns
};

//BPF_TABLE(map_type, key_type, leaf_type, table_name, num_entry)
//map <Key, Leaf>
//tracing sessions having same Key(dst_ip, src_ip, dst_port,src_port)
BPF_TABLE("hash", struct Key, struct Leaf, sessions, 1024);

/*eBPF program.
Filter IP and TCP packets, having payload not empty
and containing "HTTP", "GET", "POST" as first bytes of payload.
AND ALL the other packets having same (src_ip,dst_ip,src_port,dst_port)
this means belonging to the same "session"
this additional check avoids url truncation, if url is too long
userspace script, if necessary, reassembles urls splitted in 2 or more packets.
if the program is loaded as PROG_TYPE_SOCKET_FILTER
and attached to a socket
return 0 -> DROP the packet
return -1 -> KEEP the packet and return it to user space (userspace can read it from the socket_fd )
*/
int http_filter(struct __sk_buff *skb) {

u8 *cursor = 0;

struct ethernet_t *ethernet = cursor_advance(cursor, sizeof(*ethernet));
//filter IP packets (ethernet type = 0x0800)
if (!(ethernet->type == 0x0800)){
goto DROP;
}

struct ip_t *ip = cursor_advance(cursor, sizeof(*ip));
//filter TCP packets (ip next protocol = 0x06)
if (ip->nextp != IP_TCP) {
goto DROP;
}

u32 tcp_header_length = 0;
u32 ip_header_length = 0;
u32 payload_offset = 0;
u32 payload_length = 0;
struct Key key;
struct Leaf leaf;

struct tcp_t *tcp = cursor_advance(cursor, sizeof(*tcp));

//retrieve ip src/dest and port src/dest of current packet
//and save it into struct Key
key.dst_ip = ip->dst;
key.src_ip = ip->src;
key.dst_port = tcp->dst_port;
key.src_port = tcp->src_port;

//calculate ip header length
//value to multiply * 4
//e.g. ip->hlen = 5 ; IP Header Length = 5 x 4 byte = 20 byte
ip_header_length = ip->hlen << 2; //SHL 2 -> *4 multiply

//calculate tcp header length
//value to multiply *4
//e.g. tcp->offset = 5 ; TCP Header Length = 5 x 4 byte = 20 byte
tcp_header_length = tcp->offset << 2; //SHL 2 -> *4 multiply

//calculate patload offset and lenght
payload_offset = ETH_HLEN + ip_header_length + tcp_header_length;
payload_length = ip->tlen - ip_header_length - tcp_header_length;

//https://stackoverflow.com/questions/25047905/http-request-minimum-size-in-bytes
//minimum lenght of http request is always geater than 7 bytes
//avoid invalid access memory
//include empty payload
if(payload_length < 7){
goto DROP;
}

//load firt 7 byte of payload into payload_array
//direct access to skb not allowed
unsigned long payload_array[7];
int i = 0;
int j = 0;
for (i = payload_offset ; i < (payload_offset + 7) ; i++){
payload_array[j] = load_byte(skb , i);
j++;
}

//find a match with an HTTP message
//HTTP
if ( (payload_array[0] == 'H') && (payload_array[1] == 'T') && (payload_array[2] == 'T') && (payload_array[3] == 'P')){
goto HTTP_MATCH;
}
//GET
if ( (payload_array[0] == 'G') && (payload_array[1] == 'E') && (payload_array[2] == 'T') ){
goto HTTP_MATCH;
}
//POST
if ( (payload_array[0] == 'P') && (payload_array[1] == 'O') && (payload_array[2] == 'S') && (payload_array[3] == 'T')){
goto HTTP_MATCH;
}
//PUT
if ( (payload_array[0] == 'P') && (payload_array[1] == 'U') && (payload_array[2] == 'T') ){
goto HTTP_MATCH;
}
//DELETE
if ( (payload_array[0] == 'D') && (payload_array[1] == 'E') && (payload_array[2] == 'L') && (payload_array[3] == 'E') && (payload_array[4] == 'T') && (payload_array[5] == 'E')){
goto HTTP_MATCH;
}
//HEAD
if ( (payload_array[0] == 'H') && (payload_array[1] == 'E') && (payload_array[2] == 'A') && (payload_array[3] == 'D')){
goto HTTP_MATCH;
}

//no HTTP match
//check if packet belong to an HTTP session
struct Leaf * lookup_leaf = sessions.lookup(&key);
if(lookup_leaf){
//send packet to userspace
goto KEEP;
}
goto DROP;

//keep the packet and send it to userspace retruning -1
HTTP_MATCH:
//if not already present, insert into map <Key, Leaf>
leaf.timestamp = 0;
sessions.lookup_or_init(&key, &leaf);
sessions.update(&key,&leaf);

//send packet to userspace returning -1
KEEP:
return -1;

//drop the packet returning 0
DROP:
return 0;

}
Loading

0 comments on commit b095058

Please sign in to comment.