can i attach bpf program to all socket binding on same ip:port using so_reuseport

305 views Asked by At

From Linux manual page About so_attach_cbpf

For use with the SO_REUSEPORT option, these options allow
the user to set a classic BPF (SO_ATTACH_REUSEPORT_CBPF)
or an extended BPF (SO_ATTACH_REUSEPORT_EBPF) program
which defines how packets are assigned to the sockets in
the reuseport group (that is, all sockets which have
SO_REUSEPORT set and are using the same local address to
receive packets).

The BPF program must return an index between 0 and N-1
representing the socket which should receive the packet
(where N is the number of sockets in the group).  If the
BPF program returns an invalid index, socket selection
will fall back to the plain SO_REUSEPORT mechanism.

Sockets are numbered in the order in which they are added
to the group (that is, the order of bind(2) calls for UDP
sockets or the order of listen(2) calls for TCP sockets).
New sockets added to a reuseport group will inherit the
BPF program.  When a socket is removed from a reuseport
group (via close(2)), the last socket in the group will be
moved into the closed socket's position.

These options may be set repeatedly at any time on any
socket in the group to replace the current BPF program
used by all sockets in the group.

SO_ATTACH_REUSEPORT_CBPF takes the same argument type as
SO_ATTACH_FILTER and SO_ATTACH_REUSEPORT_EBPF takes the
same argument type as SO_ATTACH_BPF.

However when I tried to attach the bpf program to multiple sockets, I found that if there is any other udp socket listening on some ip:port and another udp socket start on same ip:port and tries to attach the bpf program, it is not able to with error bind failed: address already in use.

Simply means to change bpf program, we need to stop all udp socket then run first socket with bpf program and then again run all socket.

Here is my udp socket code:

UdpCollector(string ip, int port)
    {

        this->ip = ip;
        this->port = port;
        this->packetCount = 0;

        // initialise socket
        this->sockFD = socket(AF_INET, SOCK_DGRAM, 0);
        if (this->sockFD < 0)
        {
            perror("socket creation failed");
            exit(EXIT_FAILURE);
        }

        // Enable SO_REUSEPORT option
        const int enable = 1;
        if (setsockopt(this->sockFD, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0)
        {
            perror("setsockopt(SO_REUSEPORT) failed");
            exit(EXIT_FAILURE);
        }

        // CBPF code for REUSEPORT dispatch based on CPU() % group_size
        unsigned int groupSize=3;
        struct sock_filter code[] = {
            {BPF_LD | BPF_W | BPF_ABS, 0, 0, SKF_AD_CPU},           // A = #cpu     
            {BPF_ALU | BPF_MOD | BPF_K, 0, 0, groupSize},           // A = A % group_size
            {BPF_RET | BPF_A , 0, 0, 0}                             // return A 
        };

        struct sock_fprog bpf = {
            .len = sizeof(code)/sizeof(code[0]),
            .filter = code,
        };

        // Attach bpf program
        socklen_t sizeOfBPF = (socklen_t)sizeof(bpf);
        if (setsockopt(this->sockFD, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, &bpf, sizeOfBPF) < 0)
        {
            perror("Failed to attach so_reuseport_cbpf");
            std::cerr << "Errno: " << errno << std::endl
                      << "Error Message: " << std::strerror(errno) << std::endl;
            exit(EXIT_FAILURE);
        }

        // Configure serverAddress object
        serverAddress.sin_family = AF_INET; // address is of type ip4
        serverAddress.sin_addr.s_addr = INADDR_ANY;
        serverAddress.sin_port = htons(this->port);

        // Bind socket to specific ip and port
        if (bind(this->sockFD, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0)
        {
            perror("bind failed");
            exit(EXIT_FAILURE);
        }
    }

I want that any time I run a udp socket, I can bind a new bpf program which all other udp sockets will inherit. How to achieve that?

0

There are 0 answers