We welcome your contributions! There are multiple ways to contribute.
For bugs or enhancement requests, please file a GitHub issue unless it is security related. When filing a bug remember that the better written the bug is, the more likely it is to be fixed. If you think you have found a security vulnerability, do not raise a GitHub issue and follow the instructions in our security policy documented in SECURITY.md.
We welcome your code contributions. Before submitting code via a pull request, you will need to have signed the Oracle Contributor Agreement (OCA) at
https://oca.opensource.oracle.com/
...and your commits need to include the following line using the name and e-mail address you used to sign the OCA:
Signed-off-by: Your Name [email protected]
This can be automatically added to pull requests by committing with --sign-off or -s, e.g.
git commit --signoff
Only pull requests from committers that can be verified as having signed the OCA can be accepted.
- Ensure there is an issue created to track and discuss the fix or enhancement you intend to submit.
- Fork this repository.
- Create a branch in your fork to implement the changes. We recommend using the issue number as part of your branch name, e.g. 1234-fixes.
- Ensure that any documentation is updated with the changes that are required by your change.
- Ensure that any samples are updated if the base image has been changed.
- Ensure that all changes comply to project coding conventions as documented here
- Ensure that there is at least one test that would fail without the fix and passes post fix.
- Submit the pull request. Do not leave the pull request blank. Explain exactly what your changes are meant to do and provide simple steps on how to validate your changes. Ensure that you reference the issue you created as well.
- We will assign the pull request for review before it is submitted internally and the PR is closed.
Follow the Golden Rule. If you would like more specific guidelines, see the Contributor Covenant Code of Conduct at
https://www.contributor-covenant.org/version/1/4/code-of-conduct/
The architecture used is
- a core daemon, src/bpftune.c
- a library, libbpftune which consists of functions used by core daemon and tuners, such as logging, BPF setup etc, src/libbpftune.c; and
- a set of plug-in shared object tuners which are loaded when bpftune starts; sysctl_tuner.[bpf.]c, neigh_table_tuner.[bpf.]c
Tuners are added as plug-in .so objects built as tuner_name.c, and each tuner has a BPF program named tuner_name.bpf.c. To add a new tuner, add these files and simply add tuner_name to TUNERS in src/Makefile.
Tuners can also be built outside of bpftune; see the sample_tuner/ subdirectory for a simple example and sample Makefile.
The BPF code must
#include <bpftune/bpftune.bpf.h>
...since that header includes all relevant definitions and includes the definition of the BPF ring buffer that tuners use to communicate with userspace:
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 64 * 1024);
} ringbuf_map SEC(".maps");
On startup BPF reuses the map fd across all BPF objects; in other words they all share this ring buffer to communication with bpftune.
It also include a global variable:
unsigned int tuner_id;
When bpftune loads the tuner, it assigns this tuner id to the BPF object. This allows us to send events from the BPF programs in that object which identify the tuner source. The tuner id in the ringbuf event allows us to call the event handler callback in the appropriate tuner.
Here "legacy" implies lack of fentry, fexit, tp_btf and BPF iter programs. BPF ringbuf is assumed. For each tuner, we build a version with BPFTUNE_LEGACY defined. This corresponds to around v5.6 of the kernel, but for Oracle Linux it corresponds to UEK6U3 (v5.4-based) since it includes backports of ringbuf support.
To support per-namespace policies, support for netns cookies is required, and this is orthogonal to legacy/full support. So legacy support does not necessarily imply no netns cookie support (currently the aarch64 platform is legacy as it does not support BPF trampoline).
To test what level of bpftune support (if any) is provided on your system, run "bpftune -S"; it provides feedback like this:
$ bpftune -S
bpftune works fully
bpftune supports per-netns policy (via netns cookie)
If you add new BPF features, check the probe program probe.bpf.c; it may need updating.
If the BPF program just consists of fentry programs, simply use the BPF_FENTRY() wrapper - it will convert to kprobes for the legacy version. For other cases see these examples:
- using raw_tracepoint instead of tp_btf, see sysctl_tuner.bpf.c
- using kprobe+kretprobe instead of fexit access to calling args, see neigh_table_tuner.bpf.c
- using kretprobe instead of fexit (where no access to calling args is needed), see tcp_buffer_tuner.bpf.c
Also use BPF_CORE_READ() rather than direct dereference where possible as that will work for both kprobe and fentry for example.
For maps, use the BPF_MAP_DEF() definitions which will invoke the older libbpf map definition if using an older libbpf.
It should #include <libbpftune.h>, and must consist of the following functions
int init(struct bpftuner *tuner, int ringbuf_map_fd);
void fini(struct bpftuner *tuner);
void event_handler(struct bpftuner *tuner, struct bpftune_event *event,
void *ctx);
The init function is called on tuner initialization, and is passed the fd referring to the ring buffer map which is shared across tuners. The init() function should do any additional BPF attachment not covered by SEC() names (such as attaching to a cgroup), and initialize any global variables. All tuners should call
bpftuner_bpf_init(tuner_name, ringbuf_map_fd);
...since this loads the associated BPF skeleton. In addition, if the tuner auto-tunes any sysctls, an array of "struct bpftunable_desc":
struct bpftunable_desc {
unsigned int id;
enum bpftunable_type type;
const char *name;
__u64 flags;
__u8 num_values;
};
...should be added naming them, and
bpftuner_tunables_init(tuner, num_descs, descs);
...should be called. This informs bpftune so that if the sysctl tuner sees a modification of a sysctl that should be auto-tuned, we can disable the associated tuner. So for example if the neigh_table_tuner manages sysctl "net.ipv4.neigh.default.gc_thresh3", so if the sysctl BPF program sees it being modified, we can disable the associated neigh_table_tuner.
If any data structures are common across userspace and BPF, they should be added to a tuner_name.h file which both include.
Remember to include both the skel.h and skel.legacy.h files.
When an event the user-space component needs to know about occurs, a ringbuf event should be sent. The event structure is:
struct bpftune_event {
unsigned int tuner_id;
unsigned int scenario_id;
union {
struct bpftunable_update update[BPFTUNE_MAX_TUNABLES];
char str[BPFTUNE_MAX_NAME];
__u8 raw_data[BPFTUNE_MAX_DATA];
};
};
The scenario refers to the event type (seen packet loss to remote system), and the payload can be a string, a raw data structure etc.
When choosing BPF events to instrument, please try to avoid very high-frequency events. Try to use fentry instead of kprobe, tp_btf instead of tracepoint etc as these perform much better.
To test overhead of your tuner, there are iperf3/qperf tests that compare baseline performance versus performance when bpftune runs. For example:
$ cd test
$ TUNER=tcp_buffer_tuner.so sh iperf3_test.sh
...
$ TUNER=tcp_buffer_tuner.so sh qperf_test.sh
...
Replace TUNER value with the name of the tuner you want to assess.
Tests are mandatory for tuners; in the test directory you can see lots of examples. The test framework uses network namespaces to support iperf3 runs within the same system. Tests should validate tuning behaviour works, and ideally improves performance. In addition, ensure to test both legacy (where legacy mode is forced via "-L") and non-legacy modes. See ./TESTING.md for more details on tests.