Commit 9aab22ec authored by Oriol Arcas's avatar Oriol Arcas

Added new footer.h header where BPF_LICENSE is set if not defined

The helpers.h header specifies the BPF program license as 'GPL'.
However, other GPL-compatible licenses are possible (e.g., Dual BSD/GPL)
or even proprietary licenses (e.g., cachetop can run with a proprietary
license).

With this commit, the user can specify a BPF_LICENSE macro in the source
code:

  #define BPF_LICENSE Custom license

Note it supports multiple words and the absence of quotes. If the BPF
doesn't have a GPL-compatible license and it uses any GPL-only helpers,
the kernel will reject it with:

  cannot call GPL only function from proprietary program

If no license is specified, it will fall back to GPL (the current
behavior before this commit) so that the BCC tools and examples remain
usable.

Updated the documentation with BPF_LICENSE description, licensing error
description, and licenses for each helper.
Signed-off-by: default avatarOriol Arcas <oriol@starflownetworks.com>
parent d601984f
This diff is collapsed.
......@@ -53,6 +53,7 @@ This guide is incomplete. If something feels missing, check the bcc and kernel s
- [19. map.perf_read()](#19-mapperf_read)
- [20. map.call()](#20-mapcall)
- [21. map.redirect_map()](#21-mapredirect_map)
- [Licensing](#licensing)
- [bcc Python](#bcc-python)
- [Initialization](#initialization)
......@@ -87,6 +88,7 @@ This guide is incomplete. If something feels missing, check the bcc and kernel s
- [BPF Errors](#bpf-errors)
- [1. Invalid mem access](#1-invalid-mem-access)
- [2. Cannot call GPL only function from proprietary program](#2-cannot-call-gpl-only-function-from-proprietary-program)
# BPF C
......@@ -854,6 +856,28 @@ b.attach_xdp("eth1", out_fn, 0)
Examples in situ:
[search /examples](https://github.com/iovisor/bcc/search?l=C&q=redirect_map+path%3Aexamples&type=Code),
## Licensing
Depending on which [BPF helpers](kernel-versions.md#helpers) are used, a GPL-compatible license is required.
The special BCC macro `BPF_LICENSE` specifies the license of the BPF program. You can set the license as a comment in your source code, but the kernel has a special interface to specify it programmatically. If you need to use GPL-only helpers, it is recommended to specify the macro in your C code so that the kernel can understand it:
```C
// SPDX-License-Identifier: GPL-2.0+
#define BPF_LICENSE GPL
```
Otherwise, the kernel may reject loading your program (see the [error description](#2-cannot-call-gpl-only-function-from-proprietary-program) below). Note that it supports multiple words and quotes are not necessary:
```C
// SPDX-License-Identifier: GPL-2.0+ OR BSD-2-Clause
#define BPF_LICENSE Dual BSD/GPL
```
Check the [BPF helpers reference](kernel-versions.md#helpers) to see which helpers are GPL-only and what the kernel understands as GPL-compatible.
**If the macro is not specified, BCC will automatically define the license of the program as GPL.**
# bcc Python
## Initialization
......@@ -1521,3 +1545,16 @@ Traceback (most recent call last):
raise Exception("Failed to load BPF program %s" % func_name)
Exception: Failed to load BPF program kretprobe__inet_csk_accept
```
## 2. Cannot call GPL only function from proprietary program
This error happens when a GPL-only helper is called from a non-GPL BPF program. To fix this error, do not use GPL-only helpers from a proprietary BPF program, or relicense the BPF program under a GPL-compatible license. Check which [BPF helpers](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md#helpers) are GPL-only, and what licenses are considered GPL-compatible.
Example calling `bpf_get_stackid()`, a GPL-only BPF helper, from a proprietary program (`cflags=['-DBPF_LICENSE=Proprietary']`):
```
bpf: Failed to load program: Invalid argument
[...]
8: (85) call bpf_get_stackid#27
cannot call GPL only function from proprietary program
```
R"********(
/*
* Copyright (c) 2018 Clevernet, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef BPF_LICENSE
/* No license defined, using GPL
* Use cflags to define your own BPF_LICENSE */
#define BPF_LICENSE GPL
#endif
#define ___LICENSE(s) #s
#define __LICENSE(s) ___LICENSE(s)
#define _LICENSE __LICENSE(BPF_LICENSE)
char _license[] SEC("license") = _LICENSE;
)********"
......@@ -222,8 +222,6 @@ struct _name##_table_t _name = { .max_entries = (_max_entries) }
#define cursor_advance(_cursor, _len) \
({ void *_tmp = _cursor; _cursor += _len; _tmp; })
char _license[4] SEC("license") = "GPL";
unsigned _version SEC("version") = LINUX_VERSION_CODE;
/* helper functions called from eBPF programs written in C */
......
......@@ -43,4 +43,11 @@ map<string, const char *> ExportedFiles::headers_ = {
},
};
map<string, const char *> ExportedFiles::footers_ = {
{
"/virtual/include/bcc/footer.h",
#include "export/footer.h"
},
};
}
......@@ -23,8 +23,10 @@ namespace ebpf {
class ExportedFiles {
static std::map<std::string, const char *> headers_;
static std::map<std::string, const char *> footers_;
public:
static const std::map<std::string, const char *> & headers() { return headers_; }
static const std::map<std::string, const char *> & footers() { return footers_; }
};
}
......@@ -1287,6 +1287,10 @@ void BFrontendAction::DoMiscWorkAround() {
"#endif\n"
"#endif\n",
false);
rewriter_->getEditBuffer(rewriter_->getSourceMgr().getMainFileID()).InsertTextAfter(
rewriter_->getSourceMgr().getBuffer(rewriter_->getSourceMgr().getMainFileID())->getBufferSize(),
"\n#include <bcc/footer.h>\n");
}
void BFrontendAction::EndSourceFileAction() {
......
......@@ -70,7 +70,9 @@ ClangLoader::ClangLoader(llvm::LLVMContext *ctx, unsigned flags)
: ctx_(ctx), flags_(flags)
{
for (auto f : ExportedFiles::headers())
remapped_files_[f.first] = llvm::MemoryBuffer::getMemBuffer(f.second);
remapped_headers_[f.first] = llvm::MemoryBuffer::getMemBuffer(f.second);
for (auto f : ExportedFiles::footers())
remapped_footers_[f.first] = llvm::MemoryBuffer::getMemBuffer(f.second);
}
ClangLoader::~ClangLoader() {}
......@@ -309,7 +311,9 @@ int ClangLoader::do_compile(unique_ptr<llvm::Module> *mod, TableStorage &ts,
return -1;
invocation0.getPreprocessorOpts().RetainRemappedFileBuffers = true;
for (const auto &f : remapped_files_)
for (const auto &f : remapped_headers_)
invocation0.getPreprocessorOpts().addRemappedFile(f.first, &*f.second);
for (const auto &f : remapped_footers_)
invocation0.getPreprocessorOpts().addRemappedFile(f.first, &*f.second);
if (in_memory) {
......@@ -341,7 +345,9 @@ int ClangLoader::do_compile(unique_ptr<llvm::Module> *mod, TableStorage &ts,
// give to it. Since the embedded header files should be copied fewer times
// and reused if possible, set this flag to true.
invocation1.getPreprocessorOpts().RetainRemappedFileBuffers = true;
for (const auto &f : remapped_files_)
for (const auto &f : remapped_headers_)
invocation1.getPreprocessorOpts().addRemappedFile(f.first, &*f.second);
for (const auto &f : remapped_footers_)
invocation1.getPreprocessorOpts().addRemappedFile(f.first, &*f.second);
invocation1.getPreprocessorOpts().addRemappedFile(main_path, &*out_buf);
invocation1.getFrontendOpts().Inputs.clear();
......@@ -367,7 +373,9 @@ int ClangLoader::do_compile(unique_ptr<llvm::Module> *mod, TableStorage &ts,
const_cast<const char **>(ccargs.data()) + ccargs.size(), diags))
return -1;
invocation2.getPreprocessorOpts().RetainRemappedFileBuffers = true;
for (const auto &f : remapped_files_)
for (const auto &f : remapped_headers_)
invocation2.getPreprocessorOpts().addRemappedFile(f.first, &*f.second);
for (const auto &f : remapped_footers_)
invocation2.getPreprocessorOpts().addRemappedFile(f.first, &*f.second);
invocation2.getPreprocessorOpts().addRemappedFile(main_path, &*out_buf1);
invocation2.getFrontendOpts().Inputs.clear();
......
......@@ -66,7 +66,8 @@ class ClangLoader {
std::string &mod_src, bool use_internal_bpfh);
private:
std::map<std::string, std::unique_ptr<llvm::MemoryBuffer>> remapped_files_;
std::map<std::string, std::unique_ptr<llvm::MemoryBuffer>> remapped_headers_;
std::map<std::string, std::unique_ptr<llvm::MemoryBuffer>> remapped_footers_;
llvm::LLVMContext *ctx_;
unsigned flags_;
};
......
......@@ -75,3 +75,5 @@ add_test(NAME py_test_usdt2 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND ${TEST_WRAPPER} py_test_usdt2 sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_usdt2.py)
add_test(NAME py_test_usdt3 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND ${TEST_WRAPPER} py_test_usdt3 sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_usdt3.py)
add_test(NAME py_test_license WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND ${TEST_WRAPPER} py_test_license sudo ${CMAKE_CURRENT_SOURCE_DIR}/test_license.py)
#!/usr/bin/env python
# Copyright (c) 2018 Clevernet, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
import unittest
from bcc import BPF
class TestLicense(unittest.TestCase):
gpl_only_text = """
#include <uapi/linux/ptrace.h>
BPF_STACK_TRACE(stack_traces, 10240);
struct gpl_s {
u64 ts;
int id;
};
BPF_PERF_OUTPUT(events);
int license_program(struct pt_regs *ctx) {
struct gpl_s data = {};
data.id = stack_traces.get_stackid(ctx, BPF_F_REUSE_STACKID);
data.ts = bpf_ktime_get_ns();
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
"""
proprietary_text = """
#include <uapi/linux/ptrace.h>
struct key_t {
u64 ip;
u32 pid;
u32 uid;
char comm[16];
};
BPF_HASH(counts, struct key_t);
int license_program(struct pt_regs *ctx) {
struct key_t key = {};
u64 zero = 0 , *val;
u64 pid = bpf_get_current_pid_tgid();
u32 uid = bpf_get_current_uid_gid();
key.ip = PT_REGS_IP(ctx);
key.pid = pid & 0xFFFFFFFF;
key.uid = uid & 0xFFFFFFFF;
bpf_get_current_comm(&(key.comm), 16);
val = counts.lookup_or_init(&key, &zero); // update counter
(*val)++;
return 0;
}
"""
def license(self, lic):
return '''
#define BPF_LICENSE %s
''' % (lic)
def load_bpf_code(self, bpf_code):
event_name = bpf_code.get_syscall_fnname("read")
bpf_code.attach_kprobe(event=event_name, fn_name="license_program")
bpf_code.detach_kprobe(event=event_name)
def test_default(self):
b = BPF(text=self.gpl_only_text)
self.load_bpf_code(b)
def test_gpl_helper(self):
b = BPF(text=self.gpl_only_text, cflags=["-DBPF_LICENSE=GPL"])
self.load_bpf_code(b)
def test_gpl_helper_macro(self):
b = BPF(text=self.gpl_only_text + self.license('GPL'))
self.load_bpf_code(b)
def test_proprietary(self):
b = BPF(text=self.proprietary_text, cflags=["-DBPF_LICENSE=Proprietary"])
self.load_bpf_code(b)
def test_proprietary_macro(self):
b = BPF(text=self.proprietary_text + self.license('Proprietary'))
self.load_bpf_code(b)
def test_gpl_compatible(self):
b = BPF(text=self.gpl_only_text, cflags=["-DBPF_LICENSE=Dual BSD/GPL"])
self.load_bpf_code(b)
def test_gpl_compatible_macro(self):
b = BPF(text=self.gpl_only_text + self.license('Dual BSD/GPL'))
self.load_bpf_code(b)
def test_proprietary_words(self):
b = BPF(text=self.proprietary_text, cflags=["-DBPF_LICENSE=Proprietary license"])
self.load_bpf_code(b)
def test_proprietary_words_macro(self):
b = BPF(text=self.proprietary_text + self.license('Proprietary license'))
self.load_bpf_code(b)
@unittest.expectedFailure
def test_empty_fail(self):
b = BPF(text=self.gpl_only_text, cflags=["-DBPF_LICENSE="])
self.load_bpf_code(b)
@unittest.expectedFailure
def test_empty_fail_macro(self):
b = BPF(text=self.gpl_only_text + self.license(''))
self.load_bpf_code(b)
@unittest.expectedFailure
def test_proprietary_fail(self):
b = BPF(text=self.gpl_only_text, cflags=["-DBPF_LICENSE=Proprietary license"])
self.load_bpf_code(b)
@unittest.expectedFailure
def test_proprietary_fail_macro(self):
b = BPF(text=self.gpl_only_text + self.license('Proprietary license'))
self.load_bpf_code(b)
if __name__ == "__main__":
unittest.main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment