How to load value from memory into accumulator in bpf

118 views Asked by At

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.

1

There are 1 answers

0
pchaigno On

TL;DR. You cannot read from memory unless you first write to it. cBPF also doesn't offer a way to persist data across calls. That was one of the main additions of eBPF.


From the cBPF documentation:

The verifier will allow eBPF program to read data from stack only after
it wrote into it.
Classic BPF verifier does similar check with M[0-15] memory slots.
For example:
  bpf_ld R0 = *(u32 *)(R10 - 4)
  bpf_exit
is invalid program.

Therefore, your program is rejected because you are trying to read from memory as the very first instruction.

In any case, there is no way to persist data with cBPF. If you want to persist data across program executions, then you will have to use eBPF and its maps.