Skip to content

Commit

Permalink
feat(libsinsp): add support for containerd interface
Browse files Browse the repository at this point in the history
Signed-off-by: Roberto Scolaro <[email protected]>
  • Loading branch information
therealbobo committed Dec 9, 2024
1 parent 230ddfb commit 7c3c360
Show file tree
Hide file tree
Showing 7 changed files with 557 additions and 4 deletions.
50 changes: 50 additions & 0 deletions userspace/libsinsp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ if(NOT MINIMAL_BUILD AND NOT EMSCRIPTEN)
PRIVATE container_engine/docker/docker_linux.cpp
container_engine/docker/connection_linux.cpp
container_engine/docker/podman.cpp
container_engine/containerd.cpp
container_engine/libvirt_lxc.cpp
container_engine/lxc.cpp
container_engine/mesos.cpp
Expand Down Expand Up @@ -243,6 +244,53 @@ function(prepare_cri_grpc api_version)
endif()
endfunction()

function(prepare_containerd_grpc)
set(DEST ${CMAKE_CURRENT_BINARY_DIR}/container_engine/containerd)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/container_engine/containerd/containers.proto
${DEST}/containers.proto COPYONLY
)
add_custom_command(
OUTPUT ${DEST}/containers.grpc.pb.cc ${DEST}/containers.grpc.pb.h ${DEST}/containers.pb.cc
${DEST}/containers.pb.h
COMMENT "Generate containerd grpc code"
DEPENDS
COMMAND ${PROTOC} -I ${DEST} --cpp_out=${DEST} ${DEST}/containers.proto
COMMAND ${PROTOC} -I ${DEST} --grpc_out=. --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN}
${DEST}/containers.proto
WORKING_DIRECTORY ${DEST}
)
add_library(containerd_interface STATIC ${DEST}/containers.pb.cc ${DEST}/containers.grpc.pb.cc)
target_include_directories(containerd_interface PUBLIC $<BUILD_INTERFACE:${DEST}>)
target_link_libraries(
containerd_interface
PUBLIC "${GRPCPP_LIB}"
"${GRPC_LIB}"
"${GPR_LIB}"
"${GRPC_LIBRARIES}"
"${PROTOBUF_LIB}"
"${CARES_LIB}"
"${OPENSSL_LIBRARIES}"
)
add_dependencies(containerd_interface grpc)
install(
FILES ${DEST}/containers.grpc.pb.h ${DEST}/containers.pb.h
DESTINATION
"${CMAKE_INSTALL_INCLUDEDIR}/${LIBS_PACKAGE_NAME}/libsinsp/container_engine/containerd"
COMPONENT "scap"
)
if(NOT BUILD_SHARED_LIBS)
install(
TARGETS containerd_interface
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
COMPONENT "scap"
OPTIONAL
)
endif()
endfunction()

if(NOT EMSCRIPTEN)
add_dependencies(sinsp tbb)
endif()
Expand All @@ -260,8 +308,10 @@ if(NOT WIN32)
include(cares)
prepare_cri_grpc(v1alpha2)
prepare_cri_grpc(v1)
prepare_containerd_grpc()

target_link_libraries(sinsp PUBLIC cri_v1alpha2 cri_v1)
target_link_libraries(sinsp PUBLIC containerd_interface)

if(NOT MUSL_OPTIMIZED_BUILD)
find_library(LIB_ANL anl)
Expand Down
6 changes: 6 additions & 0 deletions userspace/libsinsp/container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ limitations under the License.
#include <libsinsp/container_engine/lxc.h>
#include <libsinsp/container_engine/mesos.h>
#include <libsinsp/container_engine/bpm.h>
#include <libsinsp/container_engine/containerd.h>
#endif // MINIMAL_BUILD
#include <libsinsp/container_engine/static_container.h>

Expand Down Expand Up @@ -599,6 +600,11 @@ void sinsp_container_manager::create_engines() {
m_container_engines.push_back(bpm_engine);
m_container_engine_by_type[CT_BPM] = bpm_engine;
}
if(m_container_engine_mask & (1 << CT_CONTAINERD)) {
auto containerd_engine = std::make_shared<container_engine::containerd>(*this);
m_container_engines.push_back(containerd_engine);
// m_container_engine_by_type[CT_CONTAINERD] = containerd_engine;
}
#endif // _WIN32
#endif // MINIMAL_BUILD
}
Expand Down
233 changes: 233 additions & 0 deletions userspace/libsinsp/container_engine/containerd.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright (C) 2024 The Falco Authors.
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.
*/

#include <sys/stat.h>

#include <libsinsp/container_engine/containerd.h>
#include <libsinsp/cri.h>
#include <libsinsp/grpc_channel_registry.h>
#include <libsinsp/runc.h>
#include <libsinsp/sinsp.h>

using namespace libsinsp::container_engine;
using namespace libsinsp::runc;

constexpr const cgroup_layout CONTAINERD_CGROUP_LAYOUT[] = {{"/default/", ""}, {nullptr, nullptr}};

constexpr const std::string_view CONTAINERD_SOCKETS[] = {
"/run/host-containerd/containerd.sock", // bottlerocket host containers socket
"/run/containerd/runtime2/containerd.sock", // tmp
};

bool containerd_interface::is_ok() {

Check warning on line 37 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L37

Added line #L37 was not covered by tests
return m_stub != nullptr;
}
containerd_interface::containerd_interface(const std::string &socket_path) {
grpc::ChannelArguments args;
args.SetInt(GRPC_ARG_ENABLE_HTTP_PROXY, 0);
std::shared_ptr<grpc::Channel> channel =

Check warning on line 43 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L43

Added line #L43 was not covered by tests
libsinsp::grpc_channel_registry::get_channel("unix://" + socket_path, &args);

m_stub = ContainerdService::Containers::NewStub(channel);

ContainerdService::ListContainersRequest req;
ContainerdService::ListContainersResponse resp;

grpc::ClientContext context;
auto deadline = std::chrono::system_clock::now() +
std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout());

Check warning on line 53 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L53

Added line #L53 was not covered by tests
context.set_deadline(deadline);
context.AddMetadata("containerd-namespace", "default");
grpc::Status status = m_stub->List(&context, req, &resp);

if(!status.ok()) {
libsinsp_logger()->format(sinsp_logger::SEV_NOTICE,
"containerd (%s): containerd runtime returned an error after "
"trying to list containerd: %s",
socket_path.c_str(),
status.error_message().c_str());
m_stub.reset(nullptr);
return;

Check warning on line 65 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L65

Added line #L65 was not covered by tests
}
}

grpc::Status containerd_interface::list_container_resp(

Check warning on line 69 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L69

Added line #L69 was not covered by tests
const std::string &container_id,
ContainerdService::ListContainersResponse &resp) {
ContainerdService::ListContainersRequest req;

Check warning on line 72 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L72

Added line #L72 was not covered by tests

std::string filter("id~=");
// REPORTED_CONTAINERD_ID_LENGTH = 12
filter.reserve(16);
filter.append(container_id);

req.add_filters(filter);
grpc::ClientContext context;
context.AddMetadata("containerd-namespace", "default");
auto deadline = std::chrono::system_clock::now() +
std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout());

Check warning on line 83 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L83

Added line #L83 was not covered by tests
context.set_deadline(deadline);
return m_stub->List(&context, req, &resp);
}

grpc::Status containerd_interface::get_container_resp(

Check warning on line 88 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L88

Added line #L88 was not covered by tests
const std::string &container_id,
ContainerdService::GetContainerResponse &resp) {
ContainerdService::GetContainerRequest req;

Check warning on line 91 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L91

Added line #L91 was not covered by tests
req.set_id(container_id);
grpc::ClientContext context;
auto deadline = std::chrono::system_clock::now() +
std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout());

Check warning on line 95 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L95

Added line #L95 was not covered by tests
context.set_deadline(deadline);
return m_stub->Get(&context, req, &resp);
}

libsinsp::container_engine::containerd::containerd(container_cache_interface &cache):
container_engine_base(cache) {
for(const auto &p : CONTAINERD_SOCKETS) {
if(p.empty()) {
continue;

Check warning on line 104 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L104

Added line #L104 was not covered by tests
}

auto socket_path = scap_get_host_root() + std::string(p);
struct stat s = {};
if(stat(socket_path.c_str(), &s) != 0 || (s.st_mode & S_IFMT) != S_IFSOCK) {
continue;
}

m_interface = std::make_unique<containerd_interface>(socket_path);
if(!m_interface->is_ok()) {
m_interface.reset(nullptr);
continue;

Check warning on line 116 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L115-L116

Added lines #L115 - L116 were not covered by tests
}
}
}

bool libsinsp::container_engine::containerd::parse_containerd(sinsp_container_info &container,

Check warning on line 121 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L121

Added line #L121 was not covered by tests
const std::string &container_id) {
// given the truncated container id, the full container id needs to be retrivied from
// containerd.
ContainerdService::ListContainersResponse resp;

Check warning on line 125 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L125

Added line #L125 was not covered by tests
grpc::Status status = m_interface->list_container_resp(container_id, resp);

if(!status.ok()) {
libsinsp_logger()->format(
sinsp_logger::SEV_DEBUG,
"containerd (%s): ListContainerResponse status error message: (%s)",
container.m_id.c_str(),
status.error_message().c_str());
return false;

Check warning on line 134 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L134

Added line #L134 was not covered by tests
}

auto containers = resp.containers();

if(containers.size() == 0) {
libsinsp_logger()->format(sinsp_logger::SEV_DEBUG,
"containerd (%s): ListContainerResponse status error message: "
"(container id has no match)",
container.m_id.c_str());
return false;
} else if(containers.size() > 1) {
libsinsp_logger()->format(sinsp_logger::SEV_DEBUG,
"containerd (%s): ListContainerResponse status error message: "
"(container id has more than one match)",
container.m_id.c_str());
return false;
}

auto raw_image_splits = sinsp_split(containers[0].image(), ':');

container.m_id = container_id;
container.m_full_id = containers[0].id();
container.m_imagerepo = raw_image_splits[0].substr(0, raw_image_splits[0].rfind("/"));
container.m_imagetag = raw_image_splits[1];
container.m_image = raw_image_splits[0].substr(raw_image_splits[0].rfind("/") + 1);
container.m_imagedigest = "";
container.m_type = CT_CONTAINERD;

Check warning on line 161 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L161

Added line #L161 was not covered by tests

for(const auto &pair : containers[0].labels()) {
if(pair.second.length() <= sinsp_container_info::m_container_label_max_length) {
container.m_labels[pair.first] = pair.second;
}
}

Json::Value spec;
Json::Reader reader;
// The spec field of the response is just a raw json.
reader.parse(containers[0].spec().value(), spec);

for(const auto &m : spec["mounts"]) {
bool readonly = false;

Check warning on line 175 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L175

Added line #L175 was not covered by tests
std::string mode;
for(const auto &jopt : m["options"]) {
std::string opt = jopt.asString();
if(opt == "ro") {
readonly = true;
} else if(opt.rfind("mode=") == 0) {
mode = opt.substr(5);
}
}
container.m_mounts.emplace_back(m["source"].asString(),
m["destination"].asString(),
mode,
!readonly,
spec["linux"]["rootfsPropagation"].asString());
}

for(const auto &env : spec["process"]["env"]) {
container.m_env.emplace_back(env.asString());
}

return true;

Check warning on line 196 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L196

Added line #L196 was not covered by tests
}

bool libsinsp::container_engine::containerd::resolve(sinsp_threadinfo *tinfo,
bool query_os_for_missing_info) {
auto container = sinsp_container_info();
std::string container_id, cgroup;

if(!matches_runc_cgroups(tinfo, CONTAINERD_CGROUP_LAYOUT, container_id, cgroup)) {
return false;
}

if(!parse_containerd(container, container_id)) {
return false;
}

libsinsp::cgroup_limits::cgroup_limits_key key(container.m_id,

Check warning on line 212 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L212

Added line #L212 was not covered by tests
tinfo->get_cgroup("cpu"),
tinfo->get_cgroup("memory"),
tinfo->get_cgroup("cpuset"));

libsinsp::cgroup_limits::cgroup_limits_value limits;
libsinsp::cgroup_limits::get_cgroup_resource_limits(key, limits);

container.m_memory_limit = limits.m_memory_limit;
container.m_cpu_shares = limits.m_cpu_shares;
container.m_cpu_quota = limits.m_cpu_quota;
container.m_cpu_period = limits.m_cpu_period;
container.m_cpuset_cpu_count = limits.m_cpuset_cpu_count;

Check warning on line 224 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L220-L224

Added lines #L220 - L224 were not covered by tests

if(container_cache().should_lookup(container.m_id, CT_CONTAINERD)) {
container.m_name = container.m_id;
container.set_lookup_status(sinsp_container_lookup::state::SUCCESSFUL);
container_cache().add_container(std::make_shared<sinsp_container_info>(container), tinfo);
container_cache().notify_new_container(container, tinfo);
}
return true;

Check warning on line 232 in userspace/libsinsp/container_engine/containerd.cpp

View check run for this annotation

Codecov / codecov/patch

userspace/libsinsp/container_engine/containerd.cpp#L232

Added line #L232 was not covered by tests
}
61 changes: 61 additions & 0 deletions userspace/libsinsp/container_engine/containerd.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright (C) 2024 The Falco Authors.
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.
*/

#pragma once

class sinsp_container_info;
class sinsp_threadinfo;

#include <libsinsp/container_engine/containerd/containers.grpc.pb.h>
#include <libsinsp/container_engine/container_engine_base.h>
#include <libsinsp/container_engine/sinsp_container_type.h>

namespace ContainerdService = containerd::services::containers::v1;

namespace libsinsp {
namespace container_engine {

class containerd_interface {
public:
containerd_interface(const std::string &socket_path);

grpc::Status list_container_resp(const std::string &container_id,
ContainerdService::ListContainersResponse &resp);

grpc::Status get_container_resp(const std::string &container_id,
ContainerdService::GetContainerResponse &resp);

bool is_ok();

private:
std::unique_ptr<ContainerdService::Containers::Stub> m_stub;
};

class containerd : public container_engine_base {
public:
containerd(container_cache_interface &cache);

bool parse_containerd(sinsp_container_info &container, const std::string &container_id);
bool resolve(sinsp_threadinfo *tinfo, bool query_os_for_missing_info) override;

private:
std::unique_ptr<containerd_interface> m_interface;
};

} // namespace container_engine
} // namespace libsinsp
Loading

0 comments on commit 7c3c360

Please sign in to comment.