rust-bpf / rust-bcc

user-friendly rust bindings for the bpf compiler collection
MIT License
475 stars 54 forks source link

Add support for attaching to sockets #180

Closed jrester closed 3 years ago

jrester commented 3 years ago

This adds support for attach_raw_socket. The interface is exposed by a new struct Socket. To attach to a socket it first has to be created the file descriptor has to be available to the user. As such I introduced BPF::get_socket_fd. I wasn't sure about the use of the feature flags so I didn't include any. Do I need to add those?

Fixes #65

An example requires libc as such I didn't include it in the PR. A possible example, which is inspired by http_filter:

main.rs:

use bcc::BPF;

pub fn main() {
    let code = include_str!("filter.c");
    let mut bpf = BPF::new(&code).unwrap();

    bcc::Socket::new()
        .handler("filter")
        .iface("lo")
        .attach(&mut bpf)
        .unwrap();

    let socket_fd = bpf.get_socket_fd().unwrap();
    let mut flags = unsafe{ libc::fcntl(socket_fd, libc::F_GETFL) };
    flags = flags & !libc::O_NONBLOCK;
    unsafe { libc::fcntl(socket_fd, libc::F_SETFL, flags); }

    loop {
        let mut buf: [u8; 2048] = [0; 2048];
        unsafe { libc::read(socket_fd, buf.as_mut_ptr() as *mut libc::c_void, 2048); }
        println!("{:?}", &buf);
    }
}

filter.c:

#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>

#define IP_TCP  6

#define DST_PORT 80

int filter(struct __sk_buffer *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;

    //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

    //check ip header length against minimum
    if (ip_header_length < sizeof(*ip)) {
            goto DROP;
    }

    //shift cursor forward for dynamic ip header size
    void *_ = cursor_advance(cursor, (ip_header_length-sizeof(*ip)));

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

    if(tcp->dst_port == DST_PORT) {
        goto END;
    } 

DROP:
    return 0;

END:
    // indicates that the packet can be passed to userspace
    return -1;
}

Just run $ curl localhost and one should see the packet content as bytes printed to stdout.

If you want, I can also add this example to the PR.

brayniac commented 3 years ago

Thanks for the PR! This will be great to add.

I would like an example added to the repo and added to the CI test suite. I think it's okay to add the dev-dependency for the example.

One thing that's unclear to me - it looks like we can only add a single BPF program that attaches to a socket? This seems like it might be a limitation for systems with multiple NICs. I'm unsure if BPF would allow us to attach multiple programs to the same interface, but if that's possible, we should allow for that here too. I think we'd just need to switch to using a HashSet<Socket> unless there are specific limitations I'm unaware of.

Thanks again!

jrester commented 3 years ago

I added the example and it now also supports multiple interfaces.

jrester commented 3 years ago

Any chance of this getting merged?

brayniac commented 3 years ago

First, thanks for this PR and for iterating on this. I do appreciate the effort to add additional functionality to this crate.

At this point, I'm concerned that this addition isn't fitting with the rest of the crate's goal of providing friendly Rust-y interface and experience for its users. I suspect this could be fixed, perhaps by using the raw socket types in socket2 crate? To be honest I'm not quite sure what the fix is going to be. Sorry that I'm unable to give some more concrete guidance on this, I'd need to spend some time to become more familiar with this BPF functionality and consider how we could provide it in a friendly way within this crate.

In the interest of preserving a consistent feel for our users, I am currently unwilling to merge this PR as it stands.

jrester commented 3 years ago

ok, than i am closing this.