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 893a76f
Show file tree
Hide file tree
Showing 7 changed files with 558 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() {
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 =
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());
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;
}
}

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

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());
context.set_deadline(deadline);
return m_stub->List(&context, req, &resp);
}

grpc::Status containerd_interface::get_container_resp(
const std::string &container_id,
ContainerdService::GetContainerResponse &resp) {
ContainerdService::GetContainerRequest req;
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());
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;
}

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;
}
}
}

bool libsinsp::container_engine::containerd::parse_containerd(sinsp_container_info &container,
const std::string &container_id) {
// given the truncated container id, the full container id needs to be retrivied from
// containerd.
ContainerdService::ListContainersResponse resp;
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;
}

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;

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;
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;
}

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,
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;

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;
}
62 changes: 62 additions & 0 deletions userspace/libsinsp/container_engine/containerd.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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 893a76f

Please sign in to comment.