android_system_bpf/libbpf_android/BpfUtils.cpp
Chenbo Feng c1dd7648b1 New bpfloader netd kernel program
Change the netd bpf program to the new format. Adding map definition and
necessary helper functions to the kernel program code. Move the netd bpf
programs out of bpfloader to a new directory.

Test: dumpsys netd trafficcontroller
Bug: 112334572
Change-Id: I9287285d188e966193532b1522b5d3e67e32e930
2019-01-07 20:17:42 -05:00

415 lines
14 KiB
C++

/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
#define LOG_TAG "BpfUtils"
#include "bpf/BpfUtils.h"
#include <elf.h>
#include <inttypes.h>
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <sstream>
#include <string>
#include <android-base/properties.h>
#include <android-base/unique_fd.h>
#include <log/log.h>
#include <netdutils/MemBlock.h>
#include <netdutils/Slice.h>
using android::base::GetUintProperty;
using android::base::unique_fd;
using android::netdutils::MemBlock;
using android::netdutils::Slice;
constexpr size_t LOG_BUF_SIZE = 65536;
namespace android {
namespace bpf {
/* The bpf_attr is a union which might have a much larger size then the struct we are using, while
* The inline initializer only reset the field we are using and leave the reset of the memory as
* is. The bpf kernel code will performs a much stricter check to ensure all unused field is 0. So
* this syscall will normally fail with E2BIG if we don't do a memset to bpf_attr.
*/
bool operator==(const StatsKey& lhs, const StatsKey& rhs) {
return ((lhs.uid == rhs.uid) && (lhs.tag == rhs.tag) && (lhs.counterSet == rhs.counterSet) &&
(lhs.ifaceIndex == rhs.ifaceIndex));
}
bool operator==(const UidTag& lhs, const UidTag& rhs) {
return ((lhs.uid == rhs.uid) && (lhs.tag == rhs.tag));
}
bool operator==(const StatsValue& lhs, const StatsValue& rhs) {
return ((lhs.rxBytes == rhs.rxBytes) && (lhs.txBytes == rhs.txBytes) &&
(lhs.rxPackets == rhs.rxPackets) && (lhs.txPackets == rhs.txPackets));
}
int bpf(int cmd, Slice bpfAttr) {
return syscall(__NR_bpf, cmd, bpfAttr.base(), bpfAttr.size());
}
int createMap(bpf_map_type map_type, uint32_t key_size, uint32_t value_size, uint32_t max_entries,
uint32_t map_flags) {
bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.map_type = map_type;
attr.key_size = key_size;
attr.value_size = value_size;
attr.max_entries = max_entries;
attr.map_flags = map_flags;
return bpf(BPF_MAP_CREATE, Slice(&attr, sizeof(attr)));
}
int writeToMapEntry(const base::unique_fd& map_fd, void* key, void* value, uint64_t flags) {
bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.map_fd = map_fd.get();
attr.key = ptr_to_u64(key);
attr.value = ptr_to_u64(value);
attr.flags = flags;
return bpf(BPF_MAP_UPDATE_ELEM, Slice(&attr, sizeof(attr)));
}
int findMapEntry(const base::unique_fd& map_fd, void* key, void* value) {
bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.map_fd = map_fd.get();
attr.key = ptr_to_u64(key);
attr.value = ptr_to_u64(value);
return bpf(BPF_MAP_LOOKUP_ELEM, Slice(&attr, sizeof(attr)));
}
int deleteMapEntry(const base::unique_fd& map_fd, void* key) {
bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.map_fd = map_fd.get();
attr.key = ptr_to_u64(key);
return bpf(BPF_MAP_DELETE_ELEM, Slice(&attr, sizeof(attr)));
}
int getNextMapKey(const base::unique_fd& map_fd, void* key, void* next_key) {
bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.map_fd = map_fd.get();
attr.key = ptr_to_u64(key);
attr.next_key = ptr_to_u64(next_key);
return bpf(BPF_MAP_GET_NEXT_KEY, Slice(&attr, sizeof(attr)));
}
int getFirstMapKey(const base::unique_fd& map_fd, void* firstKey) {
bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.map_fd = map_fd.get();
attr.key = 0;
attr.next_key = ptr_to_u64(firstKey);
return bpf(BPF_MAP_GET_NEXT_KEY, Slice(&attr, sizeof(attr)));
}
int bpfProgLoad(bpf_prog_type prog_type, Slice bpf_insns, const char* license,
uint32_t kern_version, Slice bpf_log) {
bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.prog_type = prog_type;
attr.insns = ptr_to_u64(bpf_insns.base());
attr.insn_cnt = bpf_insns.size() / sizeof(struct bpf_insn);
attr.license = ptr_to_u64((void*)license);
attr.log_buf = ptr_to_u64(bpf_log.base());
attr.log_size = bpf_log.size();
attr.log_level = DEFAULT_LOG_LEVEL;
attr.kern_version = kern_version;
int ret = bpf(BPF_PROG_LOAD, Slice(&attr, sizeof(attr)));
if (ret < 0) {
std::string prog_log = netdutils::toString(bpf_log);
std::istringstream iss(prog_log);
for (std::string line; std::getline(iss, line);) {
ALOGE("%s", line.c_str());
}
}
return ret;
}
int bpfFdPin(const base::unique_fd& map_fd, const char* pathname) {
bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.pathname = ptr_to_u64((void*)pathname);
attr.bpf_fd = map_fd.get();
return bpf(BPF_OBJ_PIN, Slice(&attr, sizeof(attr)));
}
int bpfFdGet(const char* pathname, uint32_t flag) {
bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.pathname = ptr_to_u64((void*)pathname);
attr.file_flags = flag;
return bpf(BPF_OBJ_GET, Slice(&attr, sizeof(attr)));
}
int mapRetrieve(const char* pathname, uint32_t flag) {
return bpfFdGet(pathname, flag);
}
int attachProgram(bpf_attach_type type, uint32_t prog_fd, uint32_t cg_fd) {
bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.target_fd = cg_fd;
attr.attach_bpf_fd = prog_fd;
attr.attach_type = type;
return bpf(BPF_PROG_ATTACH, Slice(&attr, sizeof(attr)));
}
int detachProgram(bpf_attach_type type, uint32_t cg_fd) {
bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.target_fd = cg_fd;
attr.attach_type = type;
return bpf(BPF_PROG_DETACH, Slice(&attr, sizeof(attr)));
}
uint64_t getSocketCookie(int sockFd) {
uint64_t sock_cookie;
socklen_t cookie_len = sizeof(sock_cookie);
int res = getsockopt(sockFd, SOL_SOCKET, SO_COOKIE, &sock_cookie, &cookie_len);
if (res < 0) {
res = -errno;
ALOGE("Failed to get socket cookie: %s\n", strerror(errno));
errno = -res;
// 0 is an invalid cookie. See sock_gen_cookie.
return NONEXISTENT_COOKIE;
}
return sock_cookie;
}
bool hasBpfSupport() {
struct utsname buf;
int kernel_version_major;
int kernel_version_minor;
uint64_t api_level = GetUintProperty<uint64_t>("ro.product.first_api_level", 0);
if (api_level == 0) {
ALOGE("Cannot determine initial API level of the device");
api_level = GetUintProperty<uint64_t>("ro.build.version.sdk", 0);
}
int ret = uname(&buf);
if (ret) {
return false;
}
char dummy;
ret = sscanf(buf.release, "%d.%d%c", &kernel_version_major, &kernel_version_minor, &dummy);
if (ret >= 2 &&
((kernel_version_major > 4) || (kernel_version_major == 4 && kernel_version_minor >= 9))) {
// Check if the device is shipped originally with android P.
return api_level >= MINIMUM_API_REQUIRED;
}
return false;
}
int loadAndPinProgram(BpfProgInfo* prog, Slice progBlock) {
// Program doesn't exist. Try to load it.
char bpf_log_buf[LOG_BUF_SIZE];
Slice bpfLog = Slice(bpf_log_buf, sizeof(bpf_log_buf));
prog->fd.reset(bpfProgLoad(prog->loadType, progBlock, "Apache 2.0", 0, bpfLog));
if (prog->fd < 0) {
int ret = -errno;
ALOGE("load %s failed: %s", prog->name, strerror(errno));
return ret;
}
if (prog->attachType == BPF_CGROUP_INET_EGRESS || prog->attachType == BPF_CGROUP_INET_INGRESS) {
unique_fd cg_fd(open(CGROUP_ROOT_PATH, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
if (cg_fd < 0) {
int ret = -errno;
ALOGE("Failed to open the cgroup directory");
return ret;
}
int ret = android::bpf::attachProgram(prog->attachType, prog->fd, cg_fd);
if (ret) {
ret = -errno;
ALOGE("%s attach failed: %s", prog->name, strerror(errno));
return ret;
}
}
if (prog->path) {
int ret = android::bpf::bpfFdPin(prog->fd, prog->path);
if (ret) {
ret = -errno;
ALOGE("Pin %s as file %s failed: %s", prog->name, prog->path, strerror(errno));
return ret;
}
}
return 0;
}
int extractAndLoadProg(BpfProgInfo* prog, Elf64_Shdr* sectionPtr, Slice fileContents,
const std::vector<BpfMapInfo>& mapPatterns) {
uint64_t progSize = (uint64_t)sectionPtr->sh_size;
Slice progSection = take(drop(fileContents, sectionPtr->sh_offset), progSize);
if (progSection.size() < progSize) {
ALOGE("programSection out of bound");
return -EINVAL;
}
MemBlock progCopy(progSection);
if (progCopy.get().size() != progSize) {
ALOGE("program cannot be extracted");
return -EINVAL;
}
Slice remaining = progCopy.get();
while (remaining.size() >= MAP_CMD_SIZE) {
// Scan the program, examining all possible places that might be the start of a
// map load operation (i.e., all bytes of value MAP_LD_CMD_HEAD).
// In each of these places, check whether it is the start of one of the patterns
// we want to replace, and if so, replace it.
Slice mapHead = findFirstMatching(remaining, MAP_LD_CMD_HEAD);
if (mapHead.size() < MAP_CMD_SIZE) break;
bool replaced = false;
for (const auto& pattern : mapPatterns) {
if (!memcmp(mapHead.base(), pattern.search.data(), MAP_CMD_SIZE)) {
memcpy(mapHead.base(), pattern.replace.data(), MAP_CMD_SIZE);
replaced = true;
break;
}
}
remaining = drop(mapHead, replaced ? MAP_CMD_SIZE : sizeof(uint8_t));
}
if (!(prog->path) || access(prog->path, R_OK) == -1) {
return loadAndPinProgram(prog, progCopy.get());
}
return 0;
}
int parsePrograms(Slice fileContents, BpfProgInfo* programs, size_t size,
const std::vector<BpfMapInfo>& mapPatterns) {
Slice elfHeader = take(fileContents, sizeof(Elf64_Ehdr));
if (elfHeader.size() < sizeof(Elf64_Ehdr)) {
ALOGE("bpf fileContents does not have complete elf header");
return -EINVAL;
}
Elf64_Ehdr* elf = (Elf64_Ehdr*)elfHeader.base();
// Find section names string table. This is the section whose index is e_shstrndx.
if (elf->e_shstrndx == SHN_UNDEF) {
ALOGE("cannot locate namesSection\n");
return -EINVAL;
}
size_t totalSectionSize = (elf->e_shnum) * sizeof(Elf64_Shdr);
Slice sections = take(drop(fileContents, elf->e_shoff), totalSectionSize);
if (sections.size() < totalSectionSize) {
ALOGE("sections corrupted");
return -EMSGSIZE;
}
Slice namesSection =
take(drop(sections, elf->e_shstrndx * sizeof(Elf64_Shdr)), sizeof(Elf64_Shdr));
if (namesSection.size() != sizeof(Elf64_Shdr)) {
ALOGE("namesSection corrupted");
return -EMSGSIZE;
}
size_t strTabOffset = ((Elf64_Shdr*)namesSection.base())->sh_offset;
size_t strTabSize = ((Elf64_Shdr*)namesSection.base())->sh_size;
Slice strTab = take(drop(fileContents, strTabOffset), strTabSize);
if (strTab.size() < strTabSize) {
ALOGE("string table out of bound\n");
return -EMSGSIZE;
}
for (int i = 0; i < elf->e_shnum; i++) {
Slice section = take(drop(sections, i * sizeof(Elf64_Shdr)), sizeof(Elf64_Shdr));
if (section.size() < sizeof(Elf64_Shdr)) {
ALOGE("section %d is out of bound, section size: %zu, header size: %zu, total size: "
"%zu",
i, section.size(), sizeof(Elf64_Shdr), sections.size());
return -EBADF;
}
Elf64_Shdr* sectionPtr = (Elf64_Shdr*)section.base();
Slice nameSlice = drop(strTab, sectionPtr->sh_name);
if (nameSlice.size() == 0) {
ALOGE("nameSlice out of bound, i: %d, strTabSize: %zu, sh_name: %u", i, strTabSize,
sectionPtr->sh_name);
return -EBADF;
}
for (size_t i = 0; i < size; i++) {
BpfProgInfo* prog = programs + i;
if (!strcmp((char*)nameSlice.base(), prog->name)) {
int ret = extractAndLoadProg(prog, sectionPtr, fileContents, mapPatterns);
if (ret) return ret;
}
}
}
// Check all the program struct passed in to make sure they all have a valid fd.
for (size_t i = 0; i < size; i++) {
BpfProgInfo* prog = programs + i;
if (access(prog->path, R_OK) == -1) {
ALOGE("Load program %s failed", prog->name);
return -EINVAL;
}
}
return 0;
}
int parseProgramsFromFile(const char* path, BpfProgInfo* programs, size_t size,
const std::vector<BpfMapInfo>& mapPatterns) {
unique_fd fd(open(path, O_RDONLY | O_CLOEXEC));
int ret;
if (fd < 0) {
ret = -errno;
ALOGE("Failed to open %s program: %s", path, strerror(errno));
return ret;
}
struct stat stat;
if (fstat(fd.get(), &stat)) {
ret = -errno;
ALOGE("Failed to get file (%s) size: %s", path, strerror(errno));
return ret;
}
off_t fileLen = stat.st_size;
char* baseAddr = (char*)mmap(NULL, fileLen, PROT_READ, MAP_PRIVATE | MAP_POPULATE, fd.get(), 0);
if (baseAddr == MAP_FAILED) {
ALOGE("Failed to map the program (%s) into memory: %s", path, strerror(errno));
ret = -errno;
return ret;
}
ret = parsePrograms(Slice(baseAddr, fileLen), programs, size, mapPatterns);
munmap(baseAddr, fileLen);
return ret;
}
} // namespace bpf
} // namespace android