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
- https://docs.ebpf.io/linux/map-type/BPF_MAP_TYPE_PERCPU_ARRAY/
- https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_CGROUP_SKB/
- https://docs.ebpf.io/linux/concepts/tail-calls/