Skip to content

Conversation

@Feanor60
Copy link

@Feanor60 Feanor60 commented Dec 6, 2022

n-acd is compiled either with functions that implement ebpf functionality using syscall or with dummy functions that always return success and set bpf file descriptor to invalid value.

It would be better to decide if the ebpf functionality is used at runtime. This can be done by ignoring failure of syscall
made in n_acd_bpf_map_create and moving on without using the bpf map. In this case bpf file descriptor should also
be set to invalid value to prevent other bpf functions from running.

n-acd is compiled either with functions that implement ebpf
functionality using syscall or with dummy functions that always
return success and set bpf file descriptor to invalid value.
It would be better to decide if the ebpf functionality is used
at runtime.

Always compile n-acd project with functions implementing ebpf
functionality. If syscall to kernel fails on n_acd_bpf_map_create
ignore the failure and continue withous using ebpf.
@dvdhrm
Copy link
Member

dvdhrm commented Dec 6, 2022

Can you elaborate on your motivation? What is your use-case? Why do you need to check for eBPF at runtime?

The current version of n-acd avoids checking for eBPF at runtime, and thus guaranteeing that it will use eBPF if configured with ebpf-support. Thus, it simplifies debugging broken systems, because you know up-front whether it uses eBPF or not.

Ideally, we would just hard-code eBPF support and require it. The fallback is currently available for older systems which do not ship the required headers and are unlikely to have runtime support for it.

Can you explain why you expect eBPF support to be available at build-time, but the binary be deployed to systems with kernels too old for eBPF?

@thom311
Copy link
Contributor

thom311 commented Dec 6, 2022

there are various cases where BPF fails. I think the main reason is if you run as non-root (e.g. only having CAP_SYS_ADMIN) or in another namespace (where you are root inside the namespace, but not root in the main namespace). This affects for example the unit tests of NetworkManager. But it also affects if you try to run NetworkManager in a system-container. Due to that, we currently build NetworkManager on RHEL and Fedora with BPF disabled. See also /proc/sys/kernel/unprivileged_bpf_disabled sysctl which causes the permission problem.

I think BPF is mainly a performance optimization to avoid wakeups. But if kernel says it doesn't work, I think we should just proceed and do the best we can (that is, filter packets in user-space).

See also https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/1049 .

Btw, Vojtech is our intern at Red Hat :)

@dvdhrm
Copy link
Member

dvdhrm commented Dec 6, 2022

there are various cases where BPF fails. I think the main reason is if you run as non-root (e.g. only having CAP_SYS_ADMIN) or in another namespace (where you are root inside the namespace, but not root in the main namespace). This affects for example the unit tests of NetworkManager. But it also affects if you try to run NetworkManager in a system-container. Due to that, we currently build NetworkManager on RHEL and Fedora with BPF disabled.

Thanks for the background! I completely forgot about the privilege-requirements. I don't see any other way than making it runtime-optional in that case.

Btw, Vojtech is our intern at Red Hat :)

Welcome to Red Hat!

I will have a look at the patches.

Copy link
Member

@dvdhrm dvdhrm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments inline. I think we also need to check whether we want to support ENOSYS for unavailable epf-syscalls. Maybe @thom311 knows whether NetworkManager supports old systems like this?

*/
r = n_acd_ensure_bpf_map_space(probe->acd);
if (r)
return r;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep this error-handler there, otherwise we will suppress real errors from the bpf-helpers.

* Add the ip address to the map, if it is not already there.
*/
if (n_acd_probe_is_unique(probe)) {
if (probe->acd->fd_bpf_map >= 0 && n_acd_probe_is_unique(probe)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would rather prefer for n_acd_bpf_map_add() to return success early if the passed map is negative. The reason is that we run in a process we do not control, so technically EUID can change during runtime and the bpf-calls might suddenly succeed. If our state always reflects the state as if the BPF map was there, then any call that suddenly succeeds in creating the BPF-map will correctly add all the IPs and have n_bpf_map reflect the real required state.

* kernel BPF map.
*/
if (n_acd_probe_is_unique(probe)) {
if (probe->acd->fd_bpf_map >= 0 && n_acd_probe_is_unique(probe)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above. I would rather make bpf_map_remove() bail out early with success if the passed map-fd is negative.

r = n_acd_bpf_compile(&fd_prog, fd_map, (struct ether_addr*) acd->mac);
if (r)
return r;
fd_prog = -1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will suppress any real error from n_acd_bpf_compile(). Why not add N_ACD_E_DENIED after N_ACD_E_DROPPED in n-acd-private.h and make n_acd_bpf_compile() return that in the exact circumstances where it matters? Then make all callers of it handle that error gracefully, just like you did here.

r = n_acd_bpf_compile(&fd_bpf_prog, acd->fd_bpf_map, (struct ether_addr*) acd->mac);
if (r)
fd_bpf_prog = -1;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, I think this should handle a possible N_ACD_E_DENIED. So something like this:

fd_bpf_prog = -1;
r = n_acd_bpf_map_create(...);
if (!r)
    r = n_acd_bpf_compile(...);
if (r && r != N_ACD_E_DENIED)
    return r;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants