Skip to content

Commit b0e0a90

Browse files
committed
feat(libsinsp): add support for containerd interface
Signed-off-by: Roberto Scolaro <roberto.scolaro21@gmail.com>
1 parent 230ddfb commit b0e0a90

File tree

7 files changed

+550
-4
lines changed

7 files changed

+550
-4
lines changed

userspace/libsinsp/CMakeLists.txt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ if(NOT MINIMAL_BUILD AND NOT EMSCRIPTEN)
128128
PRIVATE container_engine/docker/docker_linux.cpp
129129
container_engine/docker/connection_linux.cpp
130130
container_engine/docker/podman.cpp
131+
container_engine/containerd.cpp
131132
container_engine/libvirt_lxc.cpp
132133
container_engine/lxc.cpp
133134
container_engine/mesos.cpp
@@ -243,6 +244,53 @@ function(prepare_cri_grpc api_version)
243244
endif()
244245
endfunction()
245246

247+
function(prepare_containerd_grpc)
248+
set(DEST ${CMAKE_CURRENT_BINARY_DIR}/container_engine/containerd)
249+
configure_file(
250+
${CMAKE_CURRENT_SOURCE_DIR}/container_engine/containerd/containers.proto
251+
${DEST}/containers.proto COPYONLY
252+
)
253+
add_custom_command(
254+
OUTPUT ${DEST}/containers.grpc.pb.cc ${DEST}/containers.grpc.pb.h ${DEST}/containers.pb.cc
255+
${DEST}/containers.pb.h
256+
COMMENT "Generate containerd grpc code"
257+
DEPENDS
258+
COMMAND ${PROTOC} -I ${DEST} --cpp_out=${DEST} ${DEST}/containers.proto
259+
COMMAND ${PROTOC} -I ${DEST} --grpc_out=. --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN}
260+
${DEST}/containers.proto
261+
WORKING_DIRECTORY ${DEST}
262+
)
263+
add_library(containerd_interface STATIC ${DEST}/containers.pb.cc ${DEST}/containers.grpc.pb.cc)
264+
target_include_directories(containerd_interface PUBLIC $<BUILD_INTERFACE:${DEST}>)
265+
target_link_libraries(
266+
containerd_interface
267+
PUBLIC "${GRPCPP_LIB}"
268+
"${GRPC_LIB}"
269+
"${GPR_LIB}"
270+
"${GRPC_LIBRARIES}"
271+
"${PROTOBUF_LIB}"
272+
"${CARES_LIB}"
273+
"${OPENSSL_LIBRARIES}"
274+
)
275+
add_dependencies(containerd_interface grpc)
276+
install(
277+
FILES ${DEST}/containers.grpc.pb.h ${DEST}/containers.pb.h
278+
DESTINATION
279+
"${CMAKE_INSTALL_INCLUDEDIR}/${LIBS_PACKAGE_NAME}/libsinsp/container_engine/containerd"
280+
COMPONENT "scap"
281+
)
282+
if(NOT BUILD_SHARED_LIBS)
283+
install(
284+
TARGETS containerd_interface
285+
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
286+
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
287+
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
288+
COMPONENT "scap"
289+
OPTIONAL
290+
)
291+
endif()
292+
endfunction()
293+
246294
if(NOT EMSCRIPTEN)
247295
add_dependencies(sinsp tbb)
248296
endif()
@@ -260,8 +308,10 @@ if(NOT WIN32)
260308
include(cares)
261309
prepare_cri_grpc(v1alpha2)
262310
prepare_cri_grpc(v1)
311+
prepare_containerd_grpc()
263312

264313
target_link_libraries(sinsp PUBLIC cri_v1alpha2 cri_v1)
314+
target_link_libraries(sinsp PUBLIC containerd_interface)
265315

266316
if(NOT MUSL_OPTIMIZED_BUILD)
267317
find_library(LIB_ANL anl)

userspace/libsinsp/container.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ limitations under the License.
3030
#include <libsinsp/container_engine/lxc.h>
3131
#include <libsinsp/container_engine/mesos.h>
3232
#include <libsinsp/container_engine/bpm.h>
33+
#include <libsinsp/container_engine/containerd.h>
3334
#endif // MINIMAL_BUILD
3435
#include <libsinsp/container_engine/static_container.h>
3536

@@ -599,6 +600,11 @@ void sinsp_container_manager::create_engines() {
599600
m_container_engines.push_back(bpm_engine);
600601
m_container_engine_by_type[CT_BPM] = bpm_engine;
601602
}
603+
if(m_container_engine_mask & (1 << CT_CONTAINERD)) {
604+
auto containerd_engine = std::make_shared<container_engine::containerd>(*this);
605+
m_container_engines.push_back(containerd_engine);
606+
// m_container_engine_by_type[CT_CONTAINERD] = containerd_engine;
607+
}
602608
#endif // _WIN32
603609
#endif // MINIMAL_BUILD
604610
}
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
/*
3+
Copyright (C) 2024 The Falco Authors.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
17+
*/
18+
19+
#include <sys/stat.h>
20+
21+
#include <libsinsp/container_engine/containerd.h>
22+
#include <libsinsp/cri.h>
23+
#include <libsinsp/grpc_channel_registry.h>
24+
#include <libsinsp/runc.h>
25+
#include <libsinsp/sinsp.h>
26+
27+
using namespace libsinsp::container_engine;
28+
using namespace libsinsp::runc;
29+
30+
constexpr const cgroup_layout CONTAINERD_CGROUP_LAYOUT[] = {{"/default/", ""}, {nullptr, nullptr}};
31+
32+
constexpr const std::string_view CONTAINERD_SOCKETS[] = {
33+
"/run/host-containerd/containerd.sock", // bottlerocket host containers socket
34+
"/run/containerd/runtime2/containerd.sock", // tmp
35+
};
36+
37+
bool containerd_interface::is_ok() {
38+
return m_stub != nullptr;
39+
}
40+
containerd_interface::containerd_interface(const std::string &socket_path) {
41+
grpc::ChannelArguments args;
42+
args.SetInt(GRPC_ARG_ENABLE_HTTP_PROXY, 0);
43+
std::shared_ptr<grpc::Channel> channel =
44+
libsinsp::grpc_channel_registry::get_channel("unix://" + socket_path, &args);
45+
46+
m_stub = ContainerdService::Containers::NewStub(channel);
47+
48+
ContainerdService::ListContainersRequest req;
49+
ContainerdService::ListContainersResponse resp;
50+
51+
grpc::ClientContext context;
52+
auto deadline = std::chrono::system_clock::now() +
53+
std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout());
54+
context.set_deadline(deadline);
55+
56+
// The `default` namesapce is the default one of containerd
57+
// and the one used by host-containers in bottlerocket.
58+
// This is mandatory to query the containers.
59+
context.AddMetadata("containerd-namespace", "default");
60+
grpc::Status status = m_stub->List(&context, req, &resp);
61+
62+
if(!status.ok()) {
63+
libsinsp_logger()->format(sinsp_logger::SEV_NOTICE,
64+
"containerd (%s): containerd runtime returned an error after "
65+
"trying to list containerd: %s",
66+
socket_path.c_str(),
67+
status.error_message().c_str());
68+
m_stub.reset(nullptr);
69+
return;
70+
}
71+
}
72+
73+
grpc::Status containerd_interface::list_container_resp(
74+
const std::string &container_id,
75+
ContainerdService::ListContainersResponse &resp) {
76+
ContainerdService::ListContainersRequest req;
77+
78+
// To match the container using a truncated containerd id
79+
// we need to use a match filter (~=).
80+
req.add_filters("id~=" + container_id);
81+
grpc::ClientContext context;
82+
context.AddMetadata("containerd-namespace", "default");
83+
auto deadline = std::chrono::system_clock::now() +
84+
std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout());
85+
context.set_deadline(deadline);
86+
return m_stub->List(&context, req, &resp);
87+
}
88+
89+
libsinsp::container_engine::containerd::containerd(container_cache_interface &cache):
90+
container_engine_base(cache) {
91+
for(const auto &p : CONTAINERD_SOCKETS) {
92+
if(p.empty()) {
93+
continue;
94+
}
95+
96+
auto socket_path = scap_get_host_root() + std::string(p);
97+
struct stat s = {};
98+
if(stat(socket_path.c_str(), &s) != 0 || (s.st_mode & S_IFMT) != S_IFSOCK) {
99+
continue;
100+
}
101+
102+
m_interface = std::make_unique<containerd_interface>(socket_path);
103+
if(!m_interface->is_ok()) {
104+
m_interface.reset(nullptr);
105+
continue;
106+
}
107+
}
108+
}
109+
110+
bool libsinsp::container_engine::containerd::parse_containerd(sinsp_container_info &container,
111+
const std::string &container_id) {
112+
// given the truncated container id, the full container id needs to be retrivied from
113+
// containerd.
114+
ContainerdService::ListContainersResponse resp;
115+
grpc::Status status = m_interface->list_container_resp(container_id, resp);
116+
117+
if(!status.ok()) {
118+
libsinsp_logger()->format(
119+
sinsp_logger::SEV_DEBUG,
120+
"containerd (%s): ListContainerResponse status error message: (%s)",
121+
container.m_id.c_str(),
122+
status.error_message().c_str());
123+
return false;
124+
}
125+
126+
auto containers = resp.containers();
127+
128+
if(containers.size() == 0) {
129+
libsinsp_logger()->format(sinsp_logger::SEV_DEBUG,
130+
"containerd (%s): ListContainerResponse status error message: "
131+
"(container id has no match)",
132+
container.m_id.c_str());
133+
return false;
134+
} else if(containers.size() > 1) {
135+
libsinsp_logger()->format(sinsp_logger::SEV_DEBUG,
136+
"containerd (%s): ListContainerResponse status error message: "
137+
"(container id has more than one match)",
138+
container.m_id.c_str());
139+
return false;
140+
}
141+
142+
// Usually the image has this form: `docker.io/library/ubuntu:22.04`
143+
auto raw_image_splits = sinsp_split(containers[0].image(), ':');
144+
145+
container.m_id = container_id;
146+
container.m_full_id = containers[0].id();
147+
// We assume that the last `/`-separated field is the image
148+
container.m_image = raw_image_splits[0].substr(raw_image_splits[0].rfind("/") + 1);
149+
// and the first part is the repo
150+
container.m_imagerepo = raw_image_splits[0].substr(0, raw_image_splits[0].rfind("/"));
151+
container.m_imagetag = raw_image_splits[1];
152+
container.m_imagedigest = "";
153+
container.m_type = CT_CONTAINERD;
154+
155+
// Retrieve the labels.
156+
for(const auto &pair : containers[0].labels()) {
157+
if(pair.second.length() <= sinsp_container_info::m_container_label_max_length) {
158+
container.m_labels[pair.first] = pair.second;
159+
}
160+
}
161+
162+
// The spec field keeps the information about the mounts.
163+
Json::Value spec;
164+
Json::Reader reader;
165+
// The spec field of the response is just a raw json.
166+
reader.parse(containers[0].spec().value(), spec);
167+
168+
// Retrieve the mounts.
169+
for(const auto &m : spec["mounts"]) {
170+
bool readonly = false;
171+
std::string mode;
172+
for(const auto &jopt : m["options"]) {
173+
std::string opt = jopt.asString();
174+
if(opt == "ro") {
175+
readonly = true;
176+
} else if(opt.rfind("mode=") == 0) {
177+
mode = opt.substr(5);
178+
}
179+
}
180+
container.m_mounts.emplace_back(m["source"].asString(),
181+
m["destination"].asString(),
182+
mode,
183+
!readonly,
184+
spec["linux"]["rootfsPropagation"].asString());
185+
}
186+
187+
// Retrieve the env.
188+
for(const auto &env : spec["process"]["env"]) {
189+
container.m_env.emplace_back(env.asString());
190+
}
191+
192+
return true;
193+
}
194+
195+
bool libsinsp::container_engine::containerd::resolve(sinsp_threadinfo *tinfo,
196+
bool query_os_for_missing_info) {
197+
auto container = sinsp_container_info();
198+
std::string container_id, cgroup;
199+
200+
if(!matches_runc_cgroups(tinfo, CONTAINERD_CGROUP_LAYOUT, container_id, cgroup)) {
201+
return false;
202+
}
203+
204+
if(!parse_containerd(container, container_id)) {
205+
return false;
206+
}
207+
208+
libsinsp::cgroup_limits::cgroup_limits_key key(container.m_id,
209+
tinfo->get_cgroup("cpu"),
210+
tinfo->get_cgroup("memory"),
211+
tinfo->get_cgroup("cpuset"));
212+
213+
libsinsp::cgroup_limits::cgroup_limits_value limits;
214+
libsinsp::cgroup_limits::get_cgroup_resource_limits(key, limits);
215+
216+
container.m_memory_limit = limits.m_memory_limit;
217+
container.m_cpu_shares = limits.m_cpu_shares;
218+
container.m_cpu_quota = limits.m_cpu_quota;
219+
container.m_cpu_period = limits.m_cpu_period;
220+
container.m_cpuset_cpu_count = limits.m_cpuset_cpu_count;
221+
222+
if(container_cache().should_lookup(container.m_id, CT_CONTAINERD)) {
223+
container.m_name = container.m_id;
224+
container.set_lookup_status(sinsp_container_lookup::state::SUCCESSFUL);
225+
container_cache().add_container(std::make_shared<sinsp_container_info>(container), tinfo);
226+
container_cache().notify_new_container(container, tinfo);
227+
}
228+
return true;
229+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
/*
3+
Copyright (C) 2024 The Falco Authors.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
17+
*/
18+
19+
#pragma once
20+
21+
class sinsp_container_info;
22+
class sinsp_threadinfo;
23+
24+
#include <libsinsp/container_engine/containerd/containers.grpc.pb.h>
25+
#include <libsinsp/container_engine/container_engine_base.h>
26+
#include <libsinsp/container_engine/sinsp_container_type.h>
27+
28+
namespace ContainerdService = containerd::services::containers::v1;
29+
30+
namespace libsinsp {
31+
namespace container_engine {
32+
33+
class containerd_interface {
34+
public:
35+
containerd_interface(const std::string &socket_path);
36+
37+
grpc::Status list_container_resp(const std::string &container_id,
38+
ContainerdService::ListContainersResponse &resp);
39+
40+
bool is_ok();
41+
42+
private:
43+
std::unique_ptr<ContainerdService::Containers::Stub> m_stub;
44+
};
45+
46+
class containerd : public container_engine_base {
47+
public:
48+
containerd(container_cache_interface &cache);
49+
50+
bool parse_containerd(sinsp_container_info &container, const std::string &container_id);
51+
bool resolve(sinsp_threadinfo *tinfo, bool query_os_for_missing_info) override;
52+
53+
private:
54+
std::unique_ptr<containerd_interface> m_interface;
55+
};
56+
57+
} // namespace container_engine
58+
} // namespace libsinsp

0 commit comments

Comments
 (0)