inspektor-gadget

Форк
0
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.
16
package bpfiterns
17

18
import (
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"
30
	systemdDbus "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.
39
func Read(iter *link.Iter) ([]byte, error) {
40
	hostPidNs, err := host.IsHostPidNs()
41
	if err != nil {
42
		return nil, fmt.Errorf("checking if current pid namespace is host pid namespace: %w", err)
43
	}
44
	if hostPidNs {
45
		return ReadOnCurrentPidNs(iter)
46
	} else {
47
		return ReadOnHostPidNs(iter)
48
	}
49
}
50

51
// ReadOnCurrentPidNs reads the iterator in the current pid namespace.
52
func ReadOnCurrentPidNs(iter *link.Iter) ([]byte, error) {
53
	file, err := iter.Open()
54
	if err != nil {
55
		return nil, fmt.Errorf("open BPF iterator: %w", err)
56
	}
57
	defer file.Close()
58
	buf, err := io.ReadAll(file)
59
	if err != nil {
60
		return nil, fmt.Errorf("read BPF iterator: %w", err)
61
	}
62
	return 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.
69
func ReadOnHostPidNs(iter *link.Iter) ([]byte, error) {
70
	selfPidOnHost, err := os.Readlink(filepath.Join(host.HostProcFs, "self"))
71
	if err != nil {
72
		return nil, fmt.Errorf("readlink /proc/self: %w", err)
73
	}
74
	if selfPidOnHost == "" {
75
		return nil, fmt.Errorf("empty /proc/self symlink")
76
	}
77

78
	// Create a temporary directory in bpffs
79
	bpfFS := "/sys/fs/bpf"
80
	tmpPinDir, err := os.MkdirTemp(bpfFS, "ig-iter-")
81
	if err != nil {
82
		return nil, fmt.Errorf("creating temporary directory in bpffs: %w", err)
83
	}
84
	defer os.RemoveAll(tmpPinDir)
85

86
	// Prepare the pin path from the container and host point of view
87
	pinPathFromContainer := filepath.Join(tmpPinDir, "iter")
88
	pinPathFromHost := filepath.Join("/proc", selfPidOnHost, "root", pinPathFromContainer)
89

90
	err = iter.Pin(pinPathFromContainer)
91
	if err != nil {
92
		return nil, fmt.Errorf("pinning iterator: %w", err)
93
	}
94

95
	r, w, err := os.Pipe()
96
	if err != nil {
97
		return nil, fmt.Errorf("creating pipe: %w", err)
98
	}
99
	writerPath := fmt.Sprintf("/proc/%s/fd/%d", selfPidOnHost, w.Fd())
100

101
	var buf []byte
102
	var errReader error
103
	var wg sync.WaitGroup
104
	wg.Add(1)
105
	go func() {
106
		stdoutReader := 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)
109
		buf, errReader = io.ReadAll(stdoutReader)
110
		r.Close()
111
		wg.Done()
112
	}()
113

114
	conn, err := systemdDbus.NewSystemdConnectionContext(context.TODO())
115
	if err != nil {
116
		return nil, fmt.Errorf("connecting to systemd: %w", err)
117
	}
118
	defer conn.Close()
119

120
	runID := uuid.New().String()[:8]
121
	unitName := fmt.Sprintf("ig-%s.service", runID)
122

123
	statusChan := make(chan string, 1)
124
	properties := []systemdDbus.Property{
125
		systemdDbus.PropDescription("Inspektor Gadget job on host pidns"),
126
		// Type=oneshot ensures that StartTransientUnitContext will only return "done" when the job is done
127
		systemdDbus.PropType("oneshot"),
128
		systemdDbus.PropExecStart([]string{
129
			"/bin/sh",
130
			"-c",
131
			fmt.Sprintf("cat %s > %s", pinPathFromHost, writerPath),
132
		}, true),
133
	}
134

135
	_, err = conn.StartTransientUnitContext(context.TODO(),
136
		unitName, "fail", properties, statusChan)
137
	if err != nil {
138
		return nil, fmt.Errorf("starting transient unit: %w", err)
139
	}
140
	timeout := time.NewTimer(10 * time.Second)
141
	defer timeout.Stop()
142

143
	select {
144
	case s := <-statusChan:
145
		close(statusChan)
146

147
		// Close writer first: this will unblock the go routine reading from the pipe
148
		w.Close()
149
		wg.Wait()
150

151
		if errReader != nil {
152
			return 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
157
		if s != "done" {
158
			conn.ResetFailedUnitContext(context.TODO(), unitName)
159

160
			return nil, fmt.Errorf("creating systemd unit `%s`: got `%s`", unitName, s)
161
		}
162
	case <-timeout.C:
163
		w.Close()
164
		wg.Wait()
165

166
		conn.ResetFailedUnitContext(context.TODO(), unitName)
167
		return nil, errors.New("timeout waiting for systemd to create " + unitName)
168
	}
169

170
	return buf, nil
171
}
172

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.