podman

Форк
0
/
notifyproxy.go 
254 строки · 6.7 Кб
1
//go:build !windows
2

3
package notifyproxy
4

5
import (
6
	"context"
7
	"errors"
8
	"fmt"
9
	"io"
10
	"net"
11
	"os"
12
	"strings"
13
	"syscall"
14
	"time"
15

16
	"github.com/containers/podman/v5/libpod/define"
17
	"github.com/coreos/go-systemd/v22/daemon"
18
	"github.com/sirupsen/logrus"
19
	"golang.org/x/sys/unix"
20
)
21

22
const (
23
	// All constants below are defined by systemd.
24
	_notifyRcvbufSize = 8 * 1024 * 1024
25
	_notifyBufferMax  = 4096
26
	_notifyFdMax      = 768
27
	_notifyBarrierMsg = "BARRIER=1"
28
	_notifyRdyMsg     = daemon.SdNotifyReady
29
)
30

31
// SendMessage sends the specified message to the specified socket.
32
// No message is sent if no socketPath is provided and the NOTIFY_SOCKET
33
// variable is not set either.
34
func SendMessage(socketPath string, message string) error {
35
	if socketPath == "" {
36
		socketPath, _ = os.LookupEnv("NOTIFY_SOCKET")
37
		if socketPath == "" {
38
			return nil
39
		}
40
	}
41
	socketAddr := &net.UnixAddr{
42
		Name: socketPath,
43
		Net:  "unixgram",
44
	}
45
	conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr)
46
	if err != nil {
47
		return err
48
	}
49
	defer conn.Close()
50

51
	_, err = conn.Write([]byte(message))
52
	return err
53
}
54

55
// NotifyProxy can be used to proxy notify messages.
56
type NotifyProxy struct {
57
	connection *net.UnixConn
58
	socketPath string
59
	container  Container // optional
60

61
	// Channels for synchronizing the goroutine waiting for the READY
62
	// message and the one checking if the optional container is still
63
	// running.
64
	errorChan chan error
65
	readyChan chan bool
66
}
67

68
// New creates a NotifyProxy that starts listening immediately.  The specified
69
// temp directory can be left empty.
70
func New(tmpDir string) (*NotifyProxy, error) {
71
	tempFile, err := os.CreateTemp(tmpDir, "-podman-notify-proxy.sock")
72
	if err != nil {
73
		return nil, err
74
	}
75
	defer tempFile.Close()
76

77
	socketPath := tempFile.Name()
78
	if err := syscall.Unlink(socketPath); err != nil { // Unlink the socket so we can bind it
79
		return nil, err
80
	}
81

82
	socketAddr := &net.UnixAddr{
83
		Name: socketPath,
84
		Net:  "unixgram",
85
	}
86
	conn, err := net.ListenUnixgram(socketAddr.Net, socketAddr)
87
	if err != nil {
88
		return nil, err
89
	}
90

91
	if err := conn.SetReadBuffer(_notifyRcvbufSize); err != nil {
92
		return nil, fmt.Errorf("setting read buffer: %w", err)
93
	}
94

95
	errorChan := make(chan error, 1)
96
	readyChan := make(chan bool, 1)
97

98
	proxy := &NotifyProxy{
99
		connection: conn,
100
		socketPath: socketPath,
101
		errorChan:  errorChan,
102
		readyChan:  readyChan,
103
	}
104

105
	// Start waiting for the READY message in the background.  This way,
106
	// the proxy can be created prior to starting the container and
107
	// circumvents a race condition on writing/reading on the socket.
108
	proxy.listen()
109

110
	return proxy, nil
111
}
112

113
// listen waits for the READY message in the background, and process file
114
// descriptors and barriers send over the NOTIFY_SOCKET. The goroutine returns
115
// when the socket is closed.
116
func (p *NotifyProxy) listen() {
117
	go func() {
118
		// See https://github.com/containers/podman/issues/16515 for a description of the protocol.
119
		fdSize := unix.CmsgSpace(4)
120
		buffer := make([]byte, _notifyBufferMax)
121
		oob := make([]byte, _notifyFdMax*fdSize)
122
		sBuilder := strings.Builder{}
123
		for {
124
			n, oobn, flags, _, err := p.connection.ReadMsgUnix(buffer, oob)
125
			if err != nil {
126
				if !errors.Is(err, io.EOF) {
127
					p.errorChan <- err
128
					return
129
				}
130
				logrus.Errorf("Error reading unix message on socket %q: %v", p.socketPath, err)
131
				continue
132
			}
133

134
			if n > _notifyBufferMax || oobn > _notifyFdMax*fdSize {
135
				logrus.Errorf("Ignoring unix message on socket %q: incorrect number of bytes read (n=%d, oobn=%d)", p.socketPath, n, oobn)
136
				continue
137
			}
138

139
			if flags&unix.MSG_CTRUNC != 0 {
140
				logrus.Errorf("Ignoring unix message on socket %q: message truncated", p.socketPath)
141
				continue
142
			}
143

144
			sBuilder.Reset()
145
			sBuilder.Write(buffer[:n])
146
			var isBarrier, isReady bool
147

148
			for _, line := range strings.Split(sBuilder.String(), "\n") {
149
				switch line {
150
				case _notifyRdyMsg:
151
					isReady = true
152
				case _notifyBarrierMsg:
153
					isBarrier = true
154
				}
155
			}
156

157
			if isBarrier {
158
				scms, err := unix.ParseSocketControlMessage(oob)
159
				if err != nil {
160
					logrus.Errorf("parsing control message on socket %q: %v", p.socketPath, err)
161
				}
162
				for _, scm := range scms {
163
					fds, err := unix.ParseUnixRights(&scm)
164
					if err != nil {
165
						logrus.Errorf("parsing unix rights of control message on socket %q: %v", p.socketPath, err)
166
						continue
167
					}
168
					for _, fd := range fds {
169
						if err := unix.Close(fd); err != nil {
170
							logrus.Errorf("closing fd passed on socket %q: %v", fd, err)
171
							continue
172
						}
173
					}
174
				}
175
				continue
176
			}
177

178
			if isReady {
179
				p.readyChan <- true
180
			}
181
		}
182
	}()
183
}
184

185
// SocketPath returns the path of the socket the proxy is listening on.
186
func (p *NotifyProxy) SocketPath() string {
187
	return p.socketPath
188
}
189

190
// Close closes the listener and removes the socket.
191
func (p *NotifyProxy) Close() error {
192
	defer os.Remove(p.socketPath)
193
	return p.connection.Close()
194
}
195

196
// AddContainer associates a container with the proxy.
197
func (p *NotifyProxy) AddContainer(container Container) {
198
	p.container = container
199
}
200

201
// ErrNoReadyMessage is returned when we are waiting for the READY message of a
202
// container that is not in the running state anymore.
203
var ErrNoReadyMessage = errors.New("container stopped running before READY message was received")
204

205
// Container avoids a circular dependency among this package and libpod.
206
type Container interface {
207
	State() (define.ContainerStatus, error)
208
	ID() string
209
}
210

211
// Wait waits until receiving the `READY` notify message. Note that the
212
// this function must only be executed inside a systemd service which will kill
213
// the process after a given timeout. If the (optional) container stopped
214
// running before the `READY` is received, the waiting gets canceled and
215
// ErrNoReadyMessage is returned.
216
func (p *NotifyProxy) Wait() error {
217
	// If the proxy has a container we need to watch it as it may exit
218
	// without sending a READY message. The goroutine below returns when
219
	// the container exits OR when the function returns (see deferred the
220
	// cancel()) in which case we either we've either received the READY
221
	// message or encountered an error reading from the socket.
222
	if p.container != nil {
223
		// Create a cancellable context to make sure the goroutine
224
		// below terminates on function return.
225
		ctx, cancel := context.WithCancel(context.Background())
226
		defer cancel()
227
		go func() {
228
			for {
229
				select {
230
				case <-ctx.Done():
231
					return
232
				case <-time.After(time.Second):
233
					state, err := p.container.State()
234
					if err != nil {
235
						p.errorChan <- err
236
						return
237
					}
238
					if state != define.ContainerStateRunning {
239
						p.errorChan <- fmt.Errorf("%w: %s", ErrNoReadyMessage, p.container.ID())
240
						return
241
					}
242
				}
243
			}
244
		}()
245
	}
246

247
	// Wait for the ready/error channel.
248
	select {
249
	case <-p.readyChan:
250
		return nil
251
	case err := <-p.errorChan:
252
		return err
253
	}
254
}
255

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

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

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

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