inspektor-gadget
171 строка · 5.0 Кб
1// Copyright 2023 The Inspektor Gadget authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Package bpfiterns reads a ebpf iterator in a different namespace.
16package bpfiterns
17
18import (
19"bufio"
20"context"
21"errors"
22"fmt"
23"io"
24"os"
25"path/filepath"
26"sync"
27"time"
28
29"github.com/cilium/ebpf/link"
30systemdDbus "github.com/coreos/go-systemd/v22/dbus"
31_ "github.com/godbus/dbus/v5"
32"github.com/google/uuid"
33
34"github.com/inspektor-gadget/inspektor-gadget/pkg/utils/host"
35)
36
37// Read reads the iterator in the host pid namespace.
38// It will test if the current pid namespace is the host pid namespace.
39func Read(iter *link.Iter) ([]byte, error) {
40hostPidNs, err := host.IsHostPidNs()
41if err != nil {
42return nil, fmt.Errorf("checking if current pid namespace is host pid namespace: %w", err)
43}
44if hostPidNs {
45return ReadOnCurrentPidNs(iter)
46} else {
47return ReadOnHostPidNs(iter)
48}
49}
50
51// ReadOnCurrentPidNs reads the iterator in the current pid namespace.
52func ReadOnCurrentPidNs(iter *link.Iter) ([]byte, error) {
53file, err := iter.Open()
54if err != nil {
55return nil, fmt.Errorf("open BPF iterator: %w", err)
56}
57defer file.Close()
58buf, err := io.ReadAll(file)
59if err != nil {
60return nil, fmt.Errorf("read BPF iterator: %w", err)
61}
62return buf, err
63}
64
65// ReadOnHostPidNs reads the iterator in the host pid namespace.
66// It does so by pinning the iterator in a temporary directory in the host bpffs,
67// and then creating a systemd service that will read the iterator and write it
68// to a temporary pipe. The pipe is then read and returned.
69func ReadOnHostPidNs(iter *link.Iter) ([]byte, error) {
70selfPidOnHost, err := os.Readlink(filepath.Join(host.HostProcFs, "self"))
71if err != nil {
72return nil, fmt.Errorf("readlink /proc/self: %w", err)
73}
74if selfPidOnHost == "" {
75return nil, fmt.Errorf("empty /proc/self symlink")
76}
77
78// Create a temporary directory in bpffs
79bpfFS := "/sys/fs/bpf"
80tmpPinDir, err := os.MkdirTemp(bpfFS, "ig-iter-")
81if err != nil {
82return nil, fmt.Errorf("creating temporary directory in bpffs: %w", err)
83}
84defer os.RemoveAll(tmpPinDir)
85
86// Prepare the pin path from the container and host point of view
87pinPathFromContainer := filepath.Join(tmpPinDir, "iter")
88pinPathFromHost := filepath.Join("/proc", selfPidOnHost, "root", pinPathFromContainer)
89
90err = iter.Pin(pinPathFromContainer)
91if err != nil {
92return nil, fmt.Errorf("pinning iterator: %w", err)
93}
94
95r, w, err := os.Pipe()
96if err != nil {
97return nil, fmt.Errorf("creating pipe: %w", err)
98}
99writerPath := fmt.Sprintf("/proc/%s/fd/%d", selfPidOnHost, w.Fd())
100
101var buf []byte
102var errReader error
103var wg sync.WaitGroup
104wg.Add(1)
105go func() {
106stdoutReader := bufio.NewReader(r)
107// ReadAll will block until the write-side of the pipe is closed in both processes
108// (the systemd service and this process)
109buf, errReader = io.ReadAll(stdoutReader)
110r.Close()
111wg.Done()
112}()
113
114conn, err := systemdDbus.NewSystemdConnectionContext(context.TODO())
115if err != nil {
116return nil, fmt.Errorf("connecting to systemd: %w", err)
117}
118defer conn.Close()
119
120runID := uuid.New().String()[:8]
121unitName := fmt.Sprintf("ig-%s.service", runID)
122
123statusChan := make(chan string, 1)
124properties := []systemdDbus.Property{
125systemdDbus.PropDescription("Inspektor Gadget job on host pidns"),
126// Type=oneshot ensures that StartTransientUnitContext will only return "done" when the job is done
127systemdDbus.PropType("oneshot"),
128systemdDbus.PropExecStart([]string{
129"/bin/sh",
130"-c",
131fmt.Sprintf("cat %s > %s", pinPathFromHost, writerPath),
132}, true),
133}
134
135_, err = conn.StartTransientUnitContext(context.TODO(),
136unitName, "fail", properties, statusChan)
137if err != nil {
138return nil, fmt.Errorf("starting transient unit: %w", err)
139}
140timeout := time.NewTimer(10 * time.Second)
141defer timeout.Stop()
142
143select {
144case s := <-statusChan:
145close(statusChan)
146
147// Close writer first: this will unblock the go routine reading from the pipe
148w.Close()
149wg.Wait()
150
151if errReader != nil {
152return nil, fmt.Errorf("reading from pipe: %w", errReader)
153}
154
155// "done" indicates successful execution of a job
156// See https://pkg.go.dev/github.com/coreos/go-systemd/v22/dbus#Conn.StartUnit
157if s != "done" {
158conn.ResetFailedUnitContext(context.TODO(), unitName)
159
160return nil, fmt.Errorf("creating systemd unit `%s`: got `%s`", unitName, s)
161}
162case <-timeout.C:
163w.Close()
164wg.Wait()
165
166conn.ResetFailedUnitContext(context.TODO(), unitName)
167return nil, errors.New("timeout waiting for systemd to create " + unitName)
168}
169
170return buf, nil
171}
172