eBPF: Tail calls (Part-2)
Objective
- to understand/learn passing data between tail-called functions
 
Why ?
There are use-cases where tail-called function must know processing performed in previous function.
 
Reasoning
To share data among tail-called functions, we take advantage of the fact that, they are executed by same-cpu.
Refer previous post to understand performing tail-calls.
The additional things needed are:
- A 
PERCPU_ARRAY map 
- A 
tail_context struct 
When initial function is triggered, 
- Query the 
PERCPU_ARRAY map value at index 0 for tail_context 
- Reset the 
tail_context using __builtin_memset 
- Populate new values in 
tail_context 
When subsequent tail-functions are triggered
- Query the 
PERCPU_ARRAY map value at index 0 for tail_context 
- Use/Populate values from 
tail_context 
Code
| programs.h | 
|---|
 | #include "maps.h"
u8 ZERO = 0;
SEC("cgroup_skb/egress")
long egress3(struct __sk_buff* ctx){
    bpf_printk("[egress3] Someone called me");
    // Query tail_context
    struct tail_context* ctx2 = (struct tail_context*)bpf_map_lookup_elem(&tail_context_map, &ZERO);
    if(!ctx){
        return SK_PASS;
    }
    // Use tail_context values
    bpf_printk("[egress3] first=%d second=%d", ctx2->value_from_1, ctx2->value_from_1);
    return SK_PASS;
}
SEC("cgroup_skb/egress")
long egress2(struct __sk_buff* ctx){
    // Query tail context
    struct tail_context* ctx2 = (struct tail_context*)bpf_map_lookup_elem(&tail_context_map, &ZERO);
    if(!ctx){
        return SK_PASS;
    }
    // Populate tail_context
    ctx2->value_from_2 = 22;
    bpf_printk("[egress2] Calling egress3");
    bpf_tail_call(ctx, &tail_programs, TAIL_CALL_EGRESS_3)
    return SK_PASS;
}
// Initial function
SEC("cgroup_skb/egress")
long egress1(struct __sk_buff* ctx){
    // Query tail context
    struct tail_context* ctx2 = (struct tail_context*)bpf_map_lookup_elem(&tail_context_map, &ZERO);
    if(!ctx){
        return SK_PASS;
    }
    // Reset tail context
    __builtin_memset(ctx2, 0, sizeof(struct tail_context));
    ctx2->value_from_1 = 11;
    bpf_printk("[egress1] Calling egress2");
    bpf_tail_call(ctx, &tail_programs, TAIL_CALL_EGRESS_2)
    return SK_PASS;
}
  | 
 
| maps.h | 
|---|
 | // declare the index in array to use for tail-called function
#define TAIL_CALL_EGRESS_2 0 
#define TAIL_CALL_EGRESS_3 1 
// declare the prototype of function to be stored
long egress2(struct __sk_buff* ctx);
long egress3(struct __sk_buff* ctx);
// create map & initialize values
struct {
    __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
    __uint(max_entries, 1);
    __type(key, __u32);
    __array(values, long(struct __sk_buff* ctx));
} tail_programs SEC(".maps") = {
    .values = {
        [TAIL_CALL_EGRESS_2] = (void *)&egress2, 
        [TAIL_CALL_EGRESS_3] = (void *)&egress3,
    },
};  
// Custom tail_contxt
struct tail_context{
    u8 value_from_1;
    u8 value_from_2;
}
// Every CPU has its own private copy of this map
struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __uint(max_entries, 1);
    __type(key, __u32);
    __type(value, struct tail_context);
} tail_context_map SEC(".maps") 
  | 
 
Observations
PERCPU_ARRAY map has only 1 entry 
- Reset on 
tail_context is performed only initially 
Refer