Maps
Maps provide a way for eBPF programs to communicate with each other (kernel space) and with user space.
When both kernel and user space access the same maps they will need a common understanding of the key and value structures in memory. This can work if both programs are written in C and they share a header. Otherwise, both the user space language and the kernel space structures must understand the k/v structures byte-for-byte.
Maps come in a variety of types, each of which works in a slightly different way, like different data structures.
Defining maps in eBPF programs
Before we can start using maps in our eBPF programs we have to define them.
Legacy Maps
The legacy way of defining maps is to use the struct bpf_map_def
type from libbpf's eBPF side library or from the linux uapi. These map declarations should reside in the maps
ELF section. The major downside of this method is that key and value type information is lost, which is why it was replaced by BTF style maps.
struct bpf_map_def my_map = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(int),
.value_size = sizeof(int),
.max_entries = 100,
.map_flags = BPF_F_NO_PREALLOC,
} SEC("maps");
BTF Style Maps
The new way of defining eBPF maps which utilize BTF type information. See mailing list link for implementation details.
These maps should be located in the .maps
section for loaders to properly pick them up.
struct my_value { int x, y, z; };
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, int);
__type(value, struct my_value);
__uint(max_entries, 16);
} icmpcnt SEC(".maps");
The __uint
and __type
macros used in the above example are typically used to make the type definition easier to read.
They are defined in tools/lib/bpf/bpf_helpers.h
.
#define __uint(name, val) int (*name)[val]
#define __type(name, val) typeof(val) *name
#define __array(name, val) typeof(val) *name[]
The name
part of these macros refers to field names of the to be created structure. Not all names are recognized by libbpf and compatible libraries. However, the following are:
type
(__uint
) - enum, see the map types index for all valid options.max_entries
(__uint
) - int indicating the maximum amount of entries.map_flags
(__uint
) - a bitfield of flags, see flags section in map load syscall command for valid options.numa_node
(__uint
) - the ID of the NUMA node on which to place the map.key_size
(__uint
) - the size of the key in bytes. This field is mutually exclusive with thekey
field.key
(__type
) - the type of the key. This field is mutually exclusive with thekey_size
field.value_size
(__uint
) - the size of the value in bytes. This field is mutually exclusive with thevalue
andvalues
fields.value
(__type
) - the type of the value. This field is mutually exclusive with thevalue
andvalue_size
fields.values
(__array
) - see static values section. This field is mutually exclusive with thevalue
andvalue_size
field.pinning
(__uint
) -LIBBPF_PIN_BY_NAME
orLIBBPF_PIN_NONE
see pinning page for details.map_extra
(__uint
) - Addition settings, currently only used by bloom filters which use the lowest 4 bits to indicate the amount of hashes used in the bloom filter.
Typically, only the type
, key
/key_size
, value
/values
/value_size
, and max_entries
fields are required.
Static values
The values
map field has a syntax when used, it is the only field to use the __array
macro and requires us to initialize our map constant with a value. Its purpose is to populate the contents of the map during loading without having to do so manually via a userspace application. This is especially handy for users who use ip
, tc
, or bpftool
to load their programs.
The val
part of the __array
parameter should contain a type describing the individual array elements. The values we would like to pre-populate should go into the value part of the struct initialization.
The following examples show how to pre-populate a map-in-map:
struct inner_map {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, INNER_MAX_ENTRIES);
__type(key, __u32);
__type(value, __u32);
} inner_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
__uint(max_entries, MAX_ENTRIES);
__type(key, __u32);
__type(value, __u32);
__array(values, struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, INNER_MAX_ENTRIES);
__type(key, __u32);
__type(value, __u32);
});
} m_array_of_maps SEC(".maps") = {
.values = { (void *)&inner_map, 0, 0, 0, 0, 0, 0, 0, 0 },
};
Another common use is to pre-populate a tail-call map:
struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__uint(max_entries, 2);
__uint(key_size, sizeof(__u32));
__array(values, int (void *));
} prog_array_init SEC(".maps") = {
.values = {
[1] = (void *)&tailcall_1,
},
};
Creating BPF Maps
It is common for maps to be declared in the eBPF program, but maps are ultimately created from userspace. Most loader libraries pick up the map declarations from the compiled ELF file and create them automatically for the user.
However, it is also possible for users to manually create maps using the BPF_MAP_CREATE command of the BPF syscall or to use a loader library with such capabilities.
Libbpf
Libbpf is such a library, it provides the bpf_map_create
function to allow for the manual creation of maps.
LIBBPF_API int bpf_map_create(enum bpf_map_type map_type,
const char *map_name,
__u32 key_size,
__u32 value_size,
__u32 max_entries,
const struct bpf_map_create_opts *opts);
struct bpf_map_create_opts {
size_t sz; /* size of this struct for forward/backward compatibility */
__u32 btf_fd;
__u32 btf_key_type_id;
__u32 btf_value_type_id;
__u32 btf_vmlinux_value_type_id;
__u32 inner_map_fd;
__u32 map_flags;
__u64 map_extra;
__u32 numa_node;
__u32 map_ifindex;
};
The bpf_map_create
function in libbpf
can be used to create maps during runtime.
Using maps
Maps are manipulated differently from kernel space than user space.
Using from kernel space
eBPF programs can interact with maps via helper functions, these are defined in tools/lib/bpf/bpf_helpers.h
. The exact helper functions that can be used to interact with maps depend on the map type, you can reference the list of supported helper calls on the page of a given map type.
Elements of generic maps can be read using the bpf_map_lookup_elem
helper, updated with the bpf_map_update_elem
, and deleted with bpf_map_delete_elem
.
Some of these generic map types can be iterated using the bpf_for_each_map_elem
helper function.
More specialized map types might require special helpers such as bpf_redirect_map
to perform packet redirection based on the contents of a map. Or bpf_perf_event_output
to send a message via a BPF_MAP_TYPE_PERF_EVENT_ARRAY
map.
Using from user space
From user space we can also use maps in a number of ways. Most map types support reading via the BPF_MAP_LOOKUP_ELEM
syscall command, writing or updating with the BPF_MAP_UPDATE_ELEM
syscall command, and deleting with the BPF_MAP_DELETE_ELEM
syscall command. However, this does not hold true for all map types, check the page of the specific map type to see what syscall commands it supports.
Besides the single key versions, there are also batch variants of those syscall commands: BPF_MAP_LOOKUP_BATCH
, BPF_MAP_UPDATE_BATCH
, and BPF_MAP_DELETE_BATCH
. These work for a smaller sub-set of maps, again, check the specific map type for compatibility.
Most map types support iterating over keys using the BPF_MAP_GET_NEXT_KEY
syscall command.
Some map types like the BPF_MAP_TYPE_PERF_EVENT_ARRAY
require the usage of additional mechanisms like perf_event
and ring buffers to read the actual data sent via the bpf_perf_event_output
helper from the kernel side.