Skip to content

Commit 7c3c360

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

File tree

7 files changed

+557
-4
lines changed

7 files changed

+557
-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: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
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+
context.AddMetadata("containerd-namespace", "default");
56+
grpc::Status status = m_stub->List(&context, req, &resp);
57+
58+
if(!status.ok()) {
59+
libsinsp_logger()->format(sinsp_logger::SEV_NOTICE,
60+
"containerd (%s): containerd runtime returned an error after "
61+
"trying to list containerd: %s",
62+
socket_path.c_str(),
63+
status.error_message().c_str());
64+
m_stub.reset(nullptr);
65+
return;
66+
}
67+
}
68+
69+
grpc::Status containerd_interface::list_container_resp(
70+
const std::string &container_id,
71+
ContainerdService::ListContainersResponse &resp) {
72+
ContainerdService::ListContainersRequest req;
73+
74+
std::string filter("id~=");
75+
// REPORTED_CONTAINERD_ID_LENGTH = 12
76+
filter.reserve(16);
77+
filter.append(container_id);
78+
79+
req.add_filters(filter);
80+
grpc::ClientContext context;
81+
context.AddMetadata("containerd-namespace", "default");
82+
auto deadline = std::chrono::system_clock::now() +
83+
std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout());
84+
context.set_deadline(deadline);
85+
return m_stub->List(&context, req, &resp);
86+
}
87+
88+
grpc::Status containerd_interface::get_container_resp(
89+
const std::string &container_id,
90+
ContainerdService::GetContainerResponse &resp) {
91+
ContainerdService::GetContainerRequest req;
92+
req.set_id(container_id);
93+
grpc::ClientContext context;
94+
auto deadline = std::chrono::system_clock::now() +
95+
std::chrono::milliseconds(libsinsp::cri::cri_settings::get_cri_timeout());
96+
context.set_deadline(deadline);
97+
return m_stub->Get(&context, req, &resp);
98+
}
99+
100+
libsinsp::container_engine::containerd::containerd(container_cache_interface &cache):
101+
container_engine_base(cache) {
102+
for(const auto &p : CONTAINERD_SOCKETS) {
103+
if(p.empty()) {
104+
continue;
105+
}
106+
107+
auto socket_path = scap_get_host_root() + std::string(p);
108+
struct stat s = {};
109+
if(stat(socket_path.c_str(), &s) != 0 || (s.st_mode & S_IFMT) != S_IFSOCK) {
110+
continue;
111+
}
112+
113+
m_interface = std::make_unique<containerd_interface>(socket_path);
114+
if(!m_interface->is_ok()) {
115+
m_interface.reset(nullptr);
116+
continue;
117+
}
118+
}
119+
}
120+
121+
bool libsinsp::container_engine::containerd::parse_containerd(sinsp_container_info &container,
122+
const std::string &container_id) {
123+
// given the truncated container id, the full container id needs to be retrivied from
124+
// containerd.
125+
ContainerdService::ListContainersResponse resp;
126+
grpc::Status status = m_interface->list_container_resp(container_id, resp);
127+
128+
if(!status.ok()) {
129+
libsinsp_logger()->format(
130+
sinsp_logger::SEV_DEBUG,
131+
"containerd (%s): ListContainerResponse status error message: (%s)",
132+
container.m_id.c_str(),
133+
status.error_message().c_str());
134+
return false;
135+
}
136+
137+
auto containers = resp.containers();
138+
139+
if(containers.size() == 0) {
140+
libsinsp_logger()->format(sinsp_logger::SEV_DEBUG,
141+
"containerd (%s): ListContainerResponse status error message: "
142+
"(container id has no match)",
143+
container.m_id.c_str());
144+
return false;
145+
} else if(containers.size() > 1) {
146+
libsinsp_logger()->format(sinsp_logger::SEV_DEBUG,
147+
"containerd (%s): ListContainerResponse status error message: "
148+
"(container id has more than one match)",
149+
container.m_id.c_str());
150+
return false;
151+
}
152+
153+
auto raw_image_splits = sinsp_split(containers[0].image(), ':');
154+
155+
container.m_id = container_id;
156+
container.m_full_id = containers[0].id();
157+
container.m_imagerepo = raw_image_splits[0].substr(0, raw_image_splits[0].rfind("/"));
158+
container.m_imagetag = raw_image_splits[1];
159+
container.m_image = raw_image_splits[0].substr(raw_image_splits[0].rfind("/") + 1);
160+
container.m_imagedigest = "";
161+
container.m_type = CT_CONTAINERD;
162+
163+
for(const auto &pair : containers[0].labels()) {
164+
if(pair.second.length() <= sinsp_container_info::m_container_label_max_length) {
165+
container.m_labels[pair.first] = pair.second;
166+
}
167+
}
168+
169+
Json::Value spec;
170+
Json::Reader reader;
171+
// The spec field of the response is just a raw json.
172+
reader.parse(containers[0].spec().value(), spec);
173+
174+
for(const auto &m : spec["mounts"]) {
175+
bool readonly = false;
176+
std::string mode;
177+
for(const auto &jopt : m["options"]) {
178+
std::string opt = jopt.asString();
179+
if(opt == "ro") {
180+
readonly = true;
181+
} else if(opt.rfind("mode=") == 0) {
182+
mode = opt.substr(5);
183+
}
184+
}
185+
container.m_mounts.emplace_back(m["source"].asString(),
186+
m["destination"].asString(),
187+
mode,
188+
!readonly,
189+
spec["linux"]["rootfsPropagation"].asString());
190+
}
191+
192+
for(const auto &env : spec["process"]["env"]) {
193+
container.m_env.emplace_back(env.asString());
194+
}
195+
196+
return true;
197+
}
198+
199+
bool libsinsp::container_engine::containerd::resolve(sinsp_threadinfo *tinfo,
200+
bool query_os_for_missing_info) {
201+
auto container = sinsp_container_info();
202+
std::string container_id, cgroup;
203+
204+
if(!matches_runc_cgroups(tinfo, CONTAINERD_CGROUP_LAYOUT, container_id, cgroup)) {
205+
return false;
206+
}
207+
208+
if(!parse_containerd(container, container_id)) {
209+
return false;
210+
}
211+
212+
libsinsp::cgroup_limits::cgroup_limits_key key(container.m_id,
213+
tinfo->get_cgroup("cpu"),
214+
tinfo->get_cgroup("memory"),
215+
tinfo->get_cgroup("cpuset"));
216+
217+
libsinsp::cgroup_limits::cgroup_limits_value limits;
218+
libsinsp::cgroup_limits::get_cgroup_resource_limits(key, limits);
219+
220+
container.m_memory_limit = limits.m_memory_limit;
221+
container.m_cpu_shares = limits.m_cpu_shares;
222+
container.m_cpu_quota = limits.m_cpu_quota;
223+
container.m_cpu_period = limits.m_cpu_period;
224+
container.m_cpuset_cpu_count = limits.m_cpuset_cpu_count;
225+
226+
if(container_cache().should_lookup(container.m_id, CT_CONTAINERD)) {
227+
container.m_name = container.m_id;
228+
container.set_lookup_status(sinsp_container_lookup::state::SUCCESSFUL);
229+
container_cache().add_container(std::make_shared<sinsp_container_info>(container), tinfo);
230+
container_cache().notify_new_container(container, tinfo);
231+
}
232+
return true;
233+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+
grpc::Status get_container_resp(const std::string &container_id,
41+
ContainerdService::GetContainerResponse &resp);
42+
43+
bool is_ok();
44+
45+
private:
46+
std::unique_ptr<ContainerdService::Containers::Stub> m_stub;
47+
};
48+
49+
class containerd : public container_engine_base {
50+
public:
51+
containerd(container_cache_interface &cache);
52+
53+
bool parse_containerd(sinsp_container_info &container, const std::string &container_id);
54+
bool resolve(sinsp_threadinfo *tinfo, bool query_os_for_missing_info) override;
55+
56+
private:
57+
std::unique_ptr<containerd_interface> m_interface;
58+
};
59+
60+
} // namespace container_engine
61+
} // namespace libsinsp

0 commit comments

Comments
 (0)