0

I want to write a bpf program which return [1,n] in round-robin pattern. e.g if n=4 then it should return 1,2,3,4,1,2,3,4,1,2..

Algorithm for above idea is:

  1. A = read M[0]
  2. A = A % n-1
  3. A = A+1
  4. store M[0] = A
  5. return A

Apart from first step, everything is correct in my case. I am not able to load value from memory into accumulator.

BPF code

// CBPF code for REUSEPORT using plain round robin algorithm
    struct sock_filter code[] = {
        {BPF_LD | BPF_MEM, 0, 0, 0},                             // A = M[0]
        {BPF_ALU | BPF_MOD | BPF_K, 0, 0, groupSize - 1},        // A = A % group_size-1
        {BPF_ALU | BPF_ADD | BPF_K, 0, 0, 1},                    // A = A+1
        {BPF_ST, 0, 0, 0},                                       // M[0] = A
        {BPF_RET | BPF_A, 0, 0, 0}                               // return A
    };

There is problem in using BPF_LD | BPF_MEM to load value from memory into accumulator. What is correct way to do this.

Here is my complete code, you can take and run:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/filter.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>

#define BUFFER_SIZE 2048
#define EXIT_FAILURE 1
#define EXIT_SUCCESS 0

char ip[] = "0.0.0.0";
int port = 7155;
unsigned int groupSize = 3;
int sockFD;

void print_error(char* main_message)
{
    printf("%s.\nErrNo: %d. ErrMessage: %s\n",main_message, errno, strerror(errno));
}

int udp_balancer()
{
    struct sockaddr_in6 serverAddress;

    // initialise socket
    sockFD = socket(AF_INET6, SOCK_DGRAM, 0);
    if (sockFD < 0)
    {
        print_error("Socket initialisation failed");
        return EXIT_FAILURE;
    }

    // Enable SO_REUSEPORT option
    const int enable = 1;
    if (setsockopt(sockFD, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0)
    {
        print_error("setsockopt(SO_REUSEPORT) failed");
        return EXIT_FAILURE;
    }

    // CBPF code for REUSEPORT dispatch based on CPU() % group_size
    // 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 - 1},    // A = A % group_size-1
    //     {BPF_ALU | BPF_ADD | BPF_K, 0, 0, 1},                // A = A+1
    //     {BPF_RET | BPF_A, 0, 0, 0}                            // return A
    // };

    // CBPF code for REUSEPORT using plain round robin algorithm
    struct sock_filter code[] = {
        {BPF_LD | BPF_MEM, 0, 0, 0},                             // A = M[0]
        {BPF_ALU | BPF_MOD | BPF_K, 0, 0, groupSize - 1},        // A = A % group_size-1
        {BPF_ALU | BPF_ADD | BPF_K, 0, 0, 1},                    // A = A+1
        {BPF_ST, 0, 0, 0},                                       // M[0] = A
        {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(sockFD, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, &bpf, sizeOfBPF) < 0)
    {
        print_error("Bpf attach is failed");
        return EXIT_FAILURE;
    }

    // Configure serverAddress object
    memset(&serverAddress, 0, sizeof(serverAddress));
    serverAddress.sin6_family = AF_INET6; // address is of type ip6
    // serverAddress.sin6_addr = in6addr_any;
    inet_pton(AF_INET6, ip, &(serverAddress.sin6_addr));
    serverAddress.sin6_port = htons(port);

    // Bind socket to specific ip and port
    if (bind(sockFD, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0)
    {
        print_error("Address binding is failed");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

void displayConfig()
{
    pid_t pid = getpid();
    printf("FD %d  PID %d\n",sockFD,pid);
    struct sockaddr_in addr;
    socklen_t addrLen = sizeof(addr);

    // Assuming sockfd is your bound UDP socket
    if (getsockname(sockFD, (struct sockaddr *)&addr, &addrLen) == -1)
    {
        print_error("getsockname failed");
        exit(EXIT_FAILURE);
    }

    char ipStr[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &(addr.sin_addr), ipStr, INET_ADDRSTRLEN);

    printf("Listening on IP: %s::%d\n" , ipStr ,ntohs(addr.sin_port));
}

void capture()
{
    int packetCount = 0;
    struct sockaddr_in6 clientAddress;
    char buffer[BUFFER_SIZE];
    socklen_t clientAddressLength = sizeof(clientAddress);
    displayConfig();
    while (1)
    {
        // clear the buffer
        memset(buffer, 0, BUFFER_SIZE);

        // Receive packet
        ssize_t bytesRead = recvfrom(sockFD, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&clientAddress, &clientAddressLength);
        if (bytesRead < 0)
        {
            print_error("recvfrom failed");
            break;
        }

        packetCount++;
        printf("Total packet count %d\n", packetCount);
    }
}

int main()
{
    int status = udp_balancer();
    if(status == EXIT_FAILURE){
        printf("Balancer failed to run\n");
    }
    else 
        capture();
    return 0;
}

I tried running with above bpf program but it is throwing error that not able to attch bpf.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • I don't know of a way to persist data in CBPF. I would recommend looking into implementing this feature with eBPF instead which would allow you to store a running count in maps or use a helper to get a random number: https://ebpf-docs.dylanreimerink.nl/linux/program-type/BPF_PROG_TYPE_SK_REUSEPORT/ – Dylan Reimerink Jul 21 '23 at 10:46
  • We can store state. I mean from documentation it is stated that bpf program has an accumulator, an index register and a memory. We can load value from memory and also can store it. I am able to store, don't know a way to read. – Upendra Kumar Jul 21 '23 at 12:00
  • As far as I know, you have 16 spots of scratch memory M[0-15] but they don't persist between calls, they are just there to store a variable so you can use A and X for something else. https://www.kernel.org/doc/Documentation/networking/filter.txt – Dylan Reimerink Jul 21 '23 at 12:15

0 Answers0